Merge pull request #104 from kberg/master
authorRobert Konigsberg <konigsberg@google.com>
Sun, 15 Jan 2012 04:33:05 +0000 (20:33 -0800)
committerRobert Konigsberg <konigsberg@google.com>
Sun, 15 Jan 2012 04:33:05 +0000 (20:33 -0800)
Initial cut at the gallery

75 files changed:
README
auto_tests/misc/fake-jstestdriver.js
auto_tests/misc/local.html
auto_tests/tests/axis_labels.js
auto_tests/tests/date_formats.js [new file with mode: 0644]
auto_tests/tests/formats.js [new file with mode: 0644]
auto_tests/tests/interaction_model.js
auto_tests/tests/multiple_axes.js
auto_tests/tests/pathological_cases.js [new file with mode: 0644]
auto_tests/tests/sanity.js
auto_tests/tests/scientific_notation.js [new file with mode: 0644]
auto_tests/tests/scrolling_div.js
auto_tests/tests/update_options.js
docs/data.html
docs/index.html
docs/style.css
dygraph-canvas.js
dygraph-dev.js
dygraph-gviz.js
dygraph-interaction-model.js
dygraph-layout.js
dygraph-options-reference.js
dygraph-range-selector.js
dygraph-tickers.js
dygraph-utils.js
dygraph.js
experimental/palette/index.css [new file with mode: 0644]
experimental/palette/index.html [new file with mode: 0644]
experimental/palette/index.js [new file with mode: 0644]
experimental/palette/options.js [new file with mode: 0644]
experimental/palette/palette.css [new file with mode: 0644]
experimental/palette/palette.js [new file with mode: 0644]
experimental/palette/samples.js [new file with mode: 0644]
experimental/palette/textarea.css [new file with mode: 0644]
experimental/palette/textarea.js [new file with mode: 0644]
experimental/palette/tooltip.css [new file with mode: 0644]
experimental/palette/tooltip.js [new file with mode: 0644]
jshint/CHANGELOG [new file with mode: 0644]
jshint/Makefile [new file with mode: 0644]
jshint/README.markdown [new file with mode: 0755]
jshint/build/jshint-rhino.js [new file with mode: 0644]
jshint/env/jsc.js [new file with mode: 0644]
jshint/env/jsc.sh [new file with mode: 0755]
jshint/env/rhino.js [new file with mode: 0644]
jshint/env/wsh.js [new file with mode: 0644]
jshint/jshint.js [new file with mode: 0644]
lint.sh [new file with mode: 0755]
push-to-web.sh
rgbcolor/rgbcolor.js
stacktrace.js [new file with mode: 0644]
tests/avoidMinZero.html
tests/border.html
tests/color-cycle.html [new file with mode: 0644]
tests/connect-separated.html
tests/custom-bars.html
tests/draw-points.html
tests/dygraph-many-points-benchmark.html
tests/fillGraph.html
tests/fractions.html
tests/gviz-infinity.html
tests/gviz.html
tests/highlighted-region.html
tests/independent-series.html
tests/is-zoomed.html
tests/isolated-points.html
tests/labelsKMB.html
tests/negative.html
tests/no-range.html
tests/out-of-order.html
tests/small-range-zero.html
tests/two-axes.html
tests/unboxed-spark.html
tests/x-axis-formatter.html
tests/y-axis-formatter.html
tests/zero-series.html

diff --git a/README b/README
index 370db4e..dd13dce 100644 (file)
--- a/README
+++ b/README
@@ -53,13 +53,16 @@ dygraphs uses:
  - excanvas.js (Apache License)
  - YUI compressor (BSD License)
  - JsDoc Toolkit (MIT license)
-
+ - stacktrace.js is public domain
 
 automated tests use:
  - auto_tests/lib/jquery-1.4.2.js (MIT & GPL2)
  - auto_tests/lib/Asserts.js (Apache 2.0 License)
  - auto-tests/lib/JsTestDriver-1.3.3cjar (Apache 2.0 License
 
+Linter uses:
+ - JSHint (modified MIT license; prevents evil)
+
 rgbcolor: http://www.phpied.com/rgb-color-parser-in-javascript/
 strftime: http://tech.bluesmoon.info/2008/04/strftime-in-javascript.html
 excanvas: http://code.google.com/p/explorercanvas/
@@ -70,4 +73,6 @@ jquery: http://code.jquery.com/jquery-1.4.2.js
 Asserts.js: http://www.google.com/codesearch/p?#3tsINRJRCro/trunk/JsTestDriver/src/com/google/jstestdriver/javascript/Asserts.js
 JSTestDriver: http://code.google.com/p/js-test-driver/
 
+JSHint: jshint.com
+
 dygraphs is available under the MIT license, included in LICENSE.txt.
index 4a9b0ea..4536dbc 100644 (file)
@@ -39,12 +39,12 @@ var jstd = {
 var testCaseList = [];
 
 function TestCase(name) {
-  this.name = name;
-  this.toString = function() {
+  var testCase = function() { return this; };
+  testCase.name = name;
+  testCase.toString = function() {
     return "Fake test case " + name;
   };
 
-  var testCase = function() { return this; };
   testCase.prototype.setUp = function() { };
   testCase.prototype.tearDown = function() { };
   /**
@@ -84,20 +84,32 @@ function TestCase(name) {
       return false;
     }
   };
+
   testCase.prototype.runAllTests = function() {
+    var results = {};
+    var names = this.getTestNames();
+    for (var idx in names) {
+      var name = names[idx];
+      console.log("Running " + name);
+      var result = this.runTest(name);
+      results[name] = result;
+    }
+    console.log(prettyPrintEntity_(results));
+    return results;
+  };
+
+  testCase.prototype.getTestNames = function() {
     // what's better than for ... in for non-array objects?
-    var tests = {};
+    var tests = [];
     for (var name in this) {
       if (name.indexOf('test') == 0 && typeof(this[name]) == 'function') {
-        console.log("Running " + name);
-        var result = this.runTest(name);
-        tests[name] = result;
+        tests.push(name);
       }
     }
-    console.log(prettyPrintEntity_(tests));
-  };
+    return tests;
+  }
 
-  testCaseList.push(testCase);
+  testCaseList.push({name : name, testCase : testCase});
   return testCase;
 };
 
@@ -107,7 +119,7 @@ function addGlobalTestSymbols() {
 
   var num_tests = 0;
   for (var i = 0; i < testCaseList.length; i++) {
-    var tc_class = testCaseList[i];
+    var tc_class = testCaseList[i].testCase;
     for (var name in tc_class.prototype) {
       if (name.indexOf('test') == 0 && typeof(tc_class.prototype[name]) == 'function') {
         if (globalTestDb.hasOwnProperty(name)) {
@@ -128,3 +140,7 @@ function addGlobalTestSymbols() {
   console.log('Loaded ' + num_tests + ' tests in ' +
               testCaseList.length + ' test cases');
 }
+
+function getAllTestCases() {
+  return testCaseList;
+}
index 68e53b7..d24cab8 100644 (file)
@@ -22,7 +22,9 @@
   <script type="text/javascript" src="../tests/multi_csv.js"></script>
   <script type="text/javascript" src="../tests/to_dom_coords.js"></script>
   <script type="text/javascript" src="../tests/interaction_model.js"></script>
+  <!--
   <script type="text/javascript" src="../tests/tickers.js"></script>
+  -->
   <script type="text/javascript" src="../tests/scrolling_div.js"></script>
   <script type="text/javascript" src="../tests/custom_bars.js"></script>
   <script type="text/javascript" src="../tests/css.js"></script>
   <script type="text/javascript" src="../tests/rolling_average.js"></script>
   <script type="text/javascript" src="../tests/error_bars.js"></script>
   <script type="text/javascript" src="../tests/annotations.js"></script>
+  <script type="text/javascript" src="../tests/scientific_notation.js"></script>
+  <script type="text/javascript" src="../tests/pathological_cases.js"></script>
+  <script type="text/javascript" src="../tests/date_formats.js"></script>
+  <script type="text/javascript" src="../tests/formats.js"></script>
   <script type="text/javascript" src="../tests/update_options.js"></script>
   <script type="text/javascript" src="../tests/utils_test.js"></script>
   <script type="text/javascript" src="../tests/multiple_axes.js"></script>
 
   <script type="text/javascript">
   var tc = null;
+  var name = null;
   function processVariables() {
     var splitVariables = function() { // http://www.idealog.us/2006/06/javascript_to_p.html
       var query = window.location.search.substring(1); 
       var args = {};
       var vars = query.split("&"); 
-      for (var i = 0;i < vars.length; i++) { 
-        var pair = vars[i].split("="); 
-        args[pair[0]] = pair[1];
+      for (var i = 0; i < vars.length; i++) { 
+        if (vars[i].length > 0) {
+          var pair = vars[i].split("="); 
+          args[pair[0]] = pair[1];
+        }
       }
       return args;
     }
     var test = args.test;
     var command = args.command;
 
-    if (args.testCase) {
+    // args.testCaseName uses the string name of the test.
+    if (args.testCaseName) {
+      var testCases = getAllTestCases();
+      name = args.testCaseName;
+      for (var idx in testCases) {
+        var entry = testCases[idx];
+        if (entry.name == args.testCaseName) {
+          var prototype = entry.testCase;
+          tc = new entry.testCase();
+          break;
+        }
+      }
+    } else if (args.testCase) { // The class name of the test.
+      name = args.testCase;
       eval("tc = new " + args.testCase + "()");
-      if (args.command) {
-        if (args.command == "runAllTests") {
-          console.log("Running all tests for " + args.testCase);
-          tc.runAllTests();
+    }
+
+    // If the test class is defined.
+    if (tc != null) {
+      if (args.command == "runAllTests") {
+        console.log("Running all tests for " + args.testCase);
+        postResults(tc.runAllTests());
+      }
+      if (args.command == "runTest") {
+        console.log("Running test " + args.testCase + "." + args.test);
+        postResults(tc.runTest(args.test));
+      }
+    } else {
+      if (args.command == "runAllTests") {
+        console.log("Running all tests for all test cases");
+        var testCases = getAllTestCases();
+        var results = {};
+        for (var idx in testCases) {
+          var entry = testCases[idx];
+          var prototype = entry.testCase;
+          tc = new entry.testCase();
+          results[entry.name] = tc.runAllTests();
         }
-        if (args.command == "runTest") {
-          console.log("Running test " + args.testCase + "." + args.test);
-          tc.runTest(args.test);
+        postResults(results);
+      }
+    }
+  }
+
+  function postResults(results, title, div) {
+    var first = false;
+    if (div == null) {
+      var body = document.getElementsByTagName("body")[0];
+      div = document.createElement("div");
+      div.innerHTML = "Test results:";
+      if (typeof(results) != "boolean"); {
+        div.innerHTML = "Test results:<br/>";
+      }
+      body.insertBefore(div, body.firstChild);
+      first = true;
+    }
+
+    var resultToHtml = function(result) {
+      return result ?
+          "<span style='color:green;'>pass</span>" :
+          "<span style='color:red;'>fail</span>";
+    }
+
+    if (typeof(results) == "boolean") {
+      var elem = document.createElement("div");
+      if (title) {
+        elem.innerHTML = title + ": " + resultToHtml(results);
+      } else {
+        elem.innerHTML = resultToHtml(results);
+      }
+      div.appendChild(elem);
+    } else { // hash
+      var html = "";
+      for (var key in results) {
+        if (results.hasOwnProperty(key)) {
+          var elem = results[key];
+          if (typeof(elem) == "boolean" && title) {
+            postResults(results[key], title + "." + key, div);
+          } else {
+            postResults(results[key], key, div);
+          }
         }
       }
     }
+    if (first) {
+      div.appendChild(document.createElement("hr"));
+    }
   }
   </script>
 </head>
 <body>
   <div id='graph'></div>
+  <div id="selector"></div>
   <p>This file is really nothing more than all the tests coalesced into a single
-  HTML file. To run a test, open a Javascript console and execute, for
-  instance,</p>
+  HTML file. To run a test, use the selector above, or
+  open a Javascript console and execute, for instance,</p>
   <code>testDrawSimpleRangePlusOne()</code>
 
   <p>Alternatively you can use query args: <ul>
-  <li>testCase - for the name of the test case
+  <li>testCase - for the name of the test case prototype
+  <li>testCaseName - for the name of the test case
   <li>test - for the name of the test (use command=runTest)
   <li>command - either runTest or runAllTests.
   </ul>
   Example: <code>local.html?testCase=ScrollingDivTestCase&test=testNestedDiv_Scrolled&command=runTest</code>
+  <p/>
 </body>
 <script>
 processVariables();
 addGlobalTestSymbols();
+
+var selector = document.getElementById("selector");
+
+if (selector != null) { // running a test
+  var createAttached = function(name, parent) {
+    var elem = document.createElement(name);
+    parent.appendChild(elem);
+    return elem;
+  }
+
+  var description = createAttached("div", selector);
+  var list = createAttached("ul", selector);
+  var parent = list.parentElement;
+  var createLink = function(parent, text, url) {
+      var li = createAttached("li", parent);
+      var a = createAttached("a", li);
+      a.innerText = text;
+      a.href = url;
+  }
+  if (tc == null) {
+    description.innerHTML = "Test cases:";
+    var testCases = getAllTestCases();
+    createLink(list, "(run all tests)", document.URL + "?command=runAllTests");
+    for (var idx in testCases) {
+      var entryName = testCases[idx].name;
+      createLink(list, entryName, document.URL + "?testCaseName=" + entryName);
+    }
+  } else {
+    description.innerHTML = "Tests for " + name;
+    var names = tc.getTestNames();
+    createLink(list, "Run All Tests", document.URL + "&command=runAllTests");
+    for (var idx in names) {
+      var name = names[idx];
+      createLink(list, name, document.URL + "&test=" + name + "&command=runTest");
+    }
+  }
+}
 </script>
 </html>
index 3c0ad7c..6fc54e5 100644 (file)
@@ -30,11 +30,21 @@ function getXLabels() {
   return ary;
 }
 
+function makeNumbers(ary) {
+  var ret = [];
+  for (var i = 0; i < ary.length; i++) {
+    ret.push(parseFloat(ary[i]));
+  }
+  return ret;
+}
+
 function getLegend() {
   var legend = document.getElementsByClassName("dygraph-legend")[0];
   return legend.textContent;
 }
 
+AxisLabelsTestCase.prototype.kCloseFloat = 1.0e-10;
+
 AxisLabelsTestCase.prototype.testMinusOneToOne = function() {
   var opts = {
     width: 480,
@@ -87,16 +97,18 @@ AxisLabelsTestCase.prototype.testSmallRangeNearZero = function() {
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, opts);
-  assertEquals(["-0.1","-0.08","-0.06","-0.04","-0.02","0","0.02","0.04","0.06","0.08"], getYLabels());
+  assertEqualsDelta(makeNumbers(["-0.1","-0.08","-0.06","-0.04","-0.02","0","0.02","0.04","0.06","0.08"]),
+                    makeNumbers(getYLabels()), this.kCloseFloat);
 
   opts.valueRange = [-0.05, 0.05];
   g.updateOptions(opts);
   // TODO(danvk): why '1.00e-2' and not '0.01'?
-  assertEquals(["-0.05","-0.04","-0.03","-0.02","-0.01","0","1.00e-2","0.02","0.03","0.04"], getYLabels());
+  assertEquals(makeNumbers(["-0.05","-0.04","-0.03","-0.02","-0.01","0","1.00e-2","0.02","0.03","0.04"]),
+               makeNumbers(getYLabels()));
 
   opts.valueRange = [-0.01, 0.01];
   g.updateOptions(opts);
-  assertEquals(["-0.01","-8.00e-3","-6.00e-3","-4.00e-3","-2.00e-3","0","2.00e-3","4.00e-3","6.00e-3","8.00e-3"], getYLabels());
+  assertEquals(makeNumbers(["-0.01","-8.00e-3","-6.00e-3","-4.00e-3","-2.00e-3","0","2.00e-3","4.00e-3","6.00e-3","8.00e-3"]), makeNumbers(getYLabels()));
 
   g.setSelection(1);
   assertEquals('1: Y:0', getLegend());
diff --git a/auto_tests/tests/date_formats.js b/auto_tests/tests/date_formats.js
new file mode 100644 (file)
index 0000000..05a62c6
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Tests that various formats of date are understood by dygraphs.
+ *
+ * @author dan@dygraphs.com (Dan Vanderkam)
+ */
+var dateFormatsTestCase = TestCase("date-formats");
+
+dateFormatsTestCase.prototype.setUp = function() {
+};
+
+dateFormatsTestCase.prototype.tearDown = function() {
+};
+
+dateFormatsTestCase.prototype.testISO8601 = function() {
+  // Format: YYYY-MM-DDTHH:MM:SS.ddddddZ
+  // The "Z" indicates UTC, so this test should pass regardless of the time
+  // zone of the machine on which it is run.
+
+  // Firefox <4 does not support this format:
+  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/parse
+  if (navigator.userAgent.indexOf("Firefox/3.5") == -1) {
+    assertEquals(946816496789, Dygraph.dateParser("2000-01-02T12:34:56.789012Z"));
+  }
+};
+
+dateFormatsTestCase.prototype.testHyphenatedDate = function() {
+  // Format: YYYY-MM-DD HH:MM
+
+  // Midnight February 2, 2000, UTC
+  var d = new Date(Date.UTC(2000, 1, 2));
+
+  // Convert to a string in the local time zone: YYYY-DD-MM HH:MM
+  var zp = function(x) { return x < 10 ? '0' + x : x; };
+  var str = d.getFullYear() + '-' +
+            zp(1 + d.getMonth()) + '-' +
+            zp(d.getDate()) + ' ' +
+            zp(d.getHours()) + ':' +
+            zp(d.getMinutes());
+  assertEquals(Date.UTC(2000, 1, 2), Dygraph.dateParser(str));
+};
diff --git a/auto_tests/tests/formats.js b/auto_tests/tests/formats.js
new file mode 100644 (file)
index 0000000..78e2240
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Tests for data formats.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+var FormatsTestCase = TestCase("formats");
+
+FormatsTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+FormatsTestCase.prototype.tearDown = function() {
+};
+
+FormatsTestCase.prototype.dataString =
+  "X,Y\n" +
+  "0,-1\n" +
+  "1,0\n" +
+  "2,1\n" +
+  "3,0\n";
+
+FormatsTestCase.prototype.dataArray =
+  [[0,-1],
+  [1,0],
+  [2,1],
+  [3,0]];
+
+FormatsTestCase.prototype.testCsv = function() {
+  var data = this.dataString;
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  this.assertData(g);
+};
+
+FormatsTestCase.prototype.testArray = function() {
+  var data = this.dataArray;
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  this.assertData(g);
+};
+
+FormatsTestCase.prototype.testFunctionReturnsCsv = function() {
+  var string = this.dataString;
+  var data = function() { return string; };
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  // this.assertData(g);
+  console.log("x");
+};
+
+FormatsTestCase.prototype.testFunctionDefinesArray = function() {
+  var array = this.dataArray;
+  var data = function() { return array; }
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  this.assertData(g);
+};
+
+FormatsTestCase.prototype.assertData = function(g) {
+  var expected = this.dataArray;
+
+  assertEquals(4, g.numRows());
+  assertEquals(2, g.numColumns());
+
+  for (var i = 0; i < 4; i++) {
+    for (var j = 0; j < 2; j++) {
+      assertEquals(expected[i][j], g.getValue(i, j));
+    }
+  }
+}
index a363f55..8eec47c 100644 (file)
@@ -242,3 +242,92 @@ InteractionModelTestCase.prototype.testClickCallback_clickOnPoint = function() {
   assertEquals(1, clicked);
 };
 
+InteractionModelTestCase.prototype.testIsZoomed_none = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  assertFalse(g.isZoomed());
+  assertFalse(g.isZoomed("x"));
+  assertFalse(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_x = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+  DygraphOps.dispatchMouseMove_Point(g, 30, 10);
+  DygraphOps.dispatchMouseUp_Point(g, 30, 10);
+
+  assertTrue(g.isZoomed());
+  assertTrue(g.isZoomed("x"));
+  assertFalse(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_y = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+  DygraphOps.dispatchMouseMove_Point(g, 10, 30);
+  DygraphOps.dispatchMouseUp_Point(g, 10, 30);
+
+  assertTrue(g.isZoomed());
+  assertFalse(g.isZoomed("x"));
+  assertTrue(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_both = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  // Zoom x axis
+  DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+  DygraphOps.dispatchMouseMove_Point(g, 30, 10);
+  DygraphOps.dispatchMouseUp_Point(g, 30, 10);
+
+  // Now zoom y axis
+  DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+  DygraphOps.dispatchMouseMove_Point(g, 10, 30);
+  DygraphOps.dispatchMouseUp_Point(g, 10, 30);
+
+
+  assertTrue(g.isZoomed());
+  assertTrue(g.isZoomed("x"));
+  assertTrue(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_updateOptions_none = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  g.updateOptions({});
+
+  assertFalse(g.isZoomed());
+  assertFalse(g.isZoomed("x"));
+  assertFalse(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_updateOptions_x = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  g.updateOptions({dateWindow: [-.5, .3]});
+  assertTrue(g.isZoomed());
+  assertTrue(g.isZoomed("x"));
+  assertFalse(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_updateOptions_y = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  g.updateOptions({valueRange: [1, 10]});
+
+  assertTrue(g.isZoomed());
+  assertFalse(g.isZoomed("x"));
+  assertTrue(g.isZoomed("y"));
+};
+
+InteractionModelTestCase.prototype.testIsZoomed_updateOptions_both = function() {
+  var g = new Dygraph(document.getElementById("graph"), data2, {});
+
+  g.updateOptions({dateWindow: [-1, 1], valueRange: [1, 10]});
+
+  assertTrue(g.isZoomed());
+  assertTrue(g.isZoomed("x"));
+  assertTrue(g.isZoomed("y"));
+};
index 12c9c42..bf75876 100644 (file)
@@ -24,6 +24,17 @@ function getLegend() {
   return legend.textContent;
 }
 
+// returns all text in tags w/ a given css class, sorted.
+function getClassTexts(css_class) {
+  var texts = [];
+  var els = document.getElementsByClassName(css_class);
+  for (var i = 0; i < els.length; i++) {
+    texts[i] = els[i].textContent;
+  }
+  texts.sort();
+  return texts;
+}
+
 MultipleAxesTestCase.getData = function() {
   var data = [];
   for (var i = 1; i <= 100; i++) {
@@ -45,7 +56,7 @@ MultipleAxesTestCase.getData = function() {
 MultipleAxesTestCase.prototype.testBasicMultipleAxes = function() {
   var data = MultipleAxesTestCase.getData();
 
-  g = new Dygraph(
+  var g = new Dygraph(
     document.getElementById("graph"),
     data,
     {
@@ -71,7 +82,7 @@ MultipleAxesTestCase.prototype.testBasicMultipleAxes = function() {
 MultipleAxesTestCase.prototype.testNewStylePerAxisOptions = function() {
   var data = MultipleAxesTestCase.getData();
 
-  g = new Dygraph(
+  var g = new Dygraph(
     document.getElementById("graph"),
     data,
     {
@@ -101,7 +112,7 @@ MultipleAxesTestCase.prototype.testMultiAxisLayout = function() {
 
   var el = document.getElementById("graph");
 
-  g = new Dygraph(
+  var g = new Dygraph(
     el,
     data,
     {
@@ -133,3 +144,102 @@ MultipleAxesTestCase.prototype.testMultiAxisLayout = function() {
     // assertTrue((child.offsetTop + child.offsetHeight) <= 350);
   }
 };
+
+MultipleAxesTestCase.prototype.testTwoAxisVisibility = function() {
+  var data = [];
+  data.push([0,0,0]);
+  data.push([1,2,2000]);
+  data.push([2,4,1000]);
+
+  var g = new Dygraph(
+    document.getElementById("graph"),
+    data,
+    {
+      labels: [ 'X', 'bar', 'zot' ],
+      'zot': {
+        axis: {
+          labelsKMB: true
+        }
+      }
+    }
+  );
+
+  assertTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0);
+  assertTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0);
+
+  g.setVisibility(0, false);
+
+  assertTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0);
+  assertTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0);
+
+  g.setVisibility(0, true);
+  g.setVisibility(1, false);
+
+  assertTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0);
+  assertTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0);
+};
+
+// verifies that all four chart labels (title, x-, y-, y2-axis label) can be
+// used simultaneously.
+MultipleAxesTestCase.prototype.testMultiChartLabels = function() {
+  var data = MultipleAxesTestCase.getData();
+
+  var el = document.getElementById("graph");
+  el.style.border = '1px solid black';
+  el.style.marginLeft = '200px';
+  el.style.marginTop = '200px';
+
+  var g = new Dygraph(
+    el,
+    data,
+    {
+      labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+      width: 640,
+      height: 350,
+      'Y3': {
+        axis: { }
+      },
+      'Y4': {
+        axis: 'Y3'  // use the same y-axis as series Y3
+      },
+      xlabel: 'x-axis',
+      ylabel: 'y-axis',
+      y2label: 'y2-axis',
+      title: 'Chart title'
+    }
+  );
+
+  assertEquals(["Chart title", "x-axis", "y-axis", "y2-axis"],
+               getClassTexts("dygraph-label"));
+  assertEquals(["Chart title"], getClassTexts("dygraph-title"));
+  assertEquals(["x-axis"], getClassTexts("dygraph-xlabel"));
+  assertEquals(["y-axis"], getClassTexts("dygraph-ylabel"));
+  assertEquals(["y2-axis"], getClassTexts("dygraph-y2label"));
+
+  // TODO(danvk): check relative positioning here: title on top, y left of y2.
+};
+
+// Check that a chart w/o a secondary y-axis will not get a y2label, even if one
+// is specified.
+MultipleAxesTestCase.prototype.testNoY2LabelWithoutSecondaryAxis = function() {
+  var g = new Dygraph(
+    document.getElementById("graph"),
+    MultipleAxesTestCase.getData(),
+    {
+      labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+      width: 640,
+      height: 350,
+      xlabel: 'x-axis',
+      ylabel: 'y-axis',
+      y2label: 'y2-axis',
+      title: 'Chart title'
+    }
+  );
+
+  assertEquals(["Chart title", "x-axis", "y-axis"],
+               getClassTexts("dygraph-label"));
+  assertEquals(["Chart title"], getClassTexts("dygraph-title"));
+  assertEquals(["x-axis"], getClassTexts("dygraph-xlabel"));
+  assertEquals(["y-axis"], getClassTexts("dygraph-ylabel"));
+  assertEquals([], getClassTexts("dygraph-y2label"));
+};
diff --git a/auto_tests/tests/pathological_cases.js b/auto_tests/tests/pathological_cases.js
new file mode 100644 (file)
index 0000000..c134b5f
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Tests zero and one-point charts.
+ * These don't have to render nicely, they just have to not crash.
+ *
+ * @author dan@dygraphs.com (Dan Vanderkam)
+ */
+var pathologicalCasesTestCase = TestCase("pathological-cases");
+
+pathologicalCasesTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+pathologicalCasesTestCase.prototype.tearDown = function() {
+};
+
+pathologicalCasesTestCase.prototype.testZeroPoint = function() {
+  var opts = {
+    width: 480,
+    height: 320
+  };
+  var data = "X,Y\n";
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+};
+
+pathologicalCasesTestCase.prototype.testOnePoint = function() {
+  var opts = {
+    width: 480,
+    height: 320
+  };
+  var data = "X,Y\n" +
+             "1,2\n";
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+};
index 44e860c..528b7a1 100644 (file)
@@ -100,3 +100,29 @@ SanityTestCase.prototype.testToDomYCoord = function() {
     assertEqualsDelta(50 - x, g.toDomYCoord(x), 0.00001);
   }
 };
+
+/**
+ * Test that the two-argument form of the constructor (no options) works.
+ */
+SanityTestCase.prototype.testTwoArgumentConstructor = function() {
+  var graph = document.getElementById("graph");
+  new Dygraph(graph, ZERO_TO_FIFTY);
+};
+
+// Here is the first of a series of tests that just ensure the graph is drawn
+// without exception.
+//TODO(konigsberg): Move to its own test case.
+SanityTestCase.prototype.testFillStack1 = function() {
+  var graph = document.getElementById("graph");
+  new Dygraph(graph, ZERO_TO_FIFTY, { stackedGraph: true });
+}
+
+SanityTestCase.prototype.testFillStack2 = function() {
+  var graph = document.getElementById("graph");
+  new Dygraph(graph, ZERO_TO_FIFTY, { stackedGraph: true, fillGraph: true });
+}
+
+SanityTestCase.prototype.testFillStack3 = function() {
+  var graph = document.getElementById("graph");
+  new Dygraph(graph, ZERO_TO_FIFTY, { fillGraph: true });
+}
diff --git a/auto_tests/tests/scientific_notation.js b/auto_tests/tests/scientific_notation.js
new file mode 100644 (file)
index 0000000..bbdeb6a
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview Tests input data which uses scientific notation.
+ * This is a regression test for
+ * http://code.google.com/p/dygraphs/issues/detail?id=186
+ *
+ * @author danvk@google.com (Dan Vanderkam)
+ */
+var scientificNotationTestCase = TestCase("scientific-notation");
+
+scientificNotationTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+scientificNotationTestCase.prototype.tearDown = function() {
+};
+
+function getXValues(g) {
+  var xs = [];
+  for (var i = 0; i < g.numRows(); i++) {
+    xs.push(g.getValue(i, 0));
+  }
+  return xs;
+}
+
+scientificNotationTestCase.prototype.testScientificInput = function() {
+  var data = "X,Y\n" +
+      "1.0e1,-1\n" +
+      "2.0e1,0\n" +
+      "3.0e1,1\n" +
+      "4.0e1,0\n"
+  ;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  assertEqualsDelta([10, 20, 30, 40], getXValues(g), 1e-6);
+};
+
+scientificNotationTestCase.prototype.testScientificInputPlus = function() {
+  var data = "X,Y\n" +
+      "1.0e+1,-1\n" +
+      "2.0e+1,0\n" +
+      "3.0e+1,1\n" +
+      "4.0e+1,0\n"
+  ;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  assertEqualsDelta([10, 20, 30, 40], getXValues(g), 1e-6);
+};
+
+scientificNotationTestCase.prototype.testScientificInputMinus = function() {
+  var data = "X,Y\n" +
+      "1.0e-1,-1\n" +
+      "2.0e-1,0\n" +
+      "3.0e-1,1\n" +
+      "4.0e-1,0\n"
+  ;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  assertEqualsDelta([0.1, 0.2, 0.3, 0.4], getXValues(g), 1e-6);
+};
+
+scientificNotationTestCase.prototype.testScientificInputMinusCap = function() {
+  var data = "X,Y\n" +
+      "1.0E-1,-1\n" +
+      "2.0E-1,0\n" +
+      "3.0E-1,1\n" +
+      "4.0E-1,0\n"
+  ;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, {});
+  assertEqualsDelta([0.1, 0.2, 0.3, 0.4], getXValues(g), 1e-6);
+};
index 527f046..ceb2a3d 100644 (file)
@@ -50,6 +50,29 @@ var LOREM_IPSUM =
   
 };
 
+// This is usually something like 15, but for OS X Lion and its auto-hiding
+// scrollbars, it's 0. This is a large enough difference that we need to
+// consider it when synthesizing clicks.
+// Adapted from http://davidwalsh.name/detect-scrollbar-width
+ScrollingDivTestCase.prototype.detectScrollbarWidth = function() {
+  // Create the measurement node
+  var scrollDiv = document.createElement("div");
+  scrollDiv.style.width = "100px";
+  scrollDiv.style.height = "100px";
+  scrollDiv.style.overflow = "scroll";
+  scrollDiv.style.position = "absolute";
+  scrollDiv.style.top = "-9999px";
+  document.body.appendChild(scrollDiv);
+
+  // Get the scrollbar width
+  var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+
+  // Delete the DIV 
+  document.body.removeChild(scrollDiv);
+
+  return scrollbarWidth;
+};
+
 ScrollingDivTestCase.prototype.tearDown = function() {
 };
 
@@ -83,7 +106,7 @@ ScrollingDivTestCase.prototype.testScrolledDiv = function() {
 
   var clickOn4_40 = {
     clientX: 244,
-    clientY: 20,
+    clientY: 30 - this.detectScrollbarWidth(),
     screenX: 416,
     screenY: 160
   };
index b40d705..ac28e99 100644 (file)
@@ -136,11 +136,39 @@ UpdateOptionsTestCase.prototype.testUpdateColors = function() {
   graph.updateOptions({ colors: colors1 });
   assertEquals(colors1, graph.getColors());
 
-  var colors2 = [ "#aaa", "#bbb", "#ccc" ];
+  // extra colors are ignored until you add additional data series.
+  var colors2 = [ "#ddd", "#eee", "#fff" ];
   graph.updateOptions({ colors: colors2 });
-  assertEquals(colors2, graph.getColors());
+  assertEquals(["#ddd", "#eee"], graph.getColors());
 
+  graph.updateOptions({ file:
+      "X,Y1,Y2,Y3\n" +
+      "2011-01-01,2,3,4\n" +
+      "2011-02-02,5,3,2\n"
+  });
+  assertEquals(colors2, graph.getColors());
 
-  graph.updateOptions({ colors: null });
+  graph.updateOptions({ colors: null, file: this.data });
   assertEquals(defaultColors, graph.getColors());
 }
+
+// Regression test for http://code.google.com/p/dygraphs/issues/detail?id=249
+// Verifies that setting 'legend: always' via update immediately shows the
+// legend.
+UpdateOptionsTestCase.prototype.testUpdateLegendAlways = function() {
+  var graphDiv = document.getElementById("graph");
+  var graph = new Dygraph(graphDiv, this.data, this.opts);
+
+  var legend = document.getElementsByClassName("dygraph-legend");
+  assertEquals(1, legend.length);
+  legend = legend[0];
+  assertEquals("", legend.innerHTML);
+
+  graph.updateOptions({legend: 'always'});
+
+  legend = document.getElementsByClassName("dygraph-legend");
+  assertEquals(1, legend.length);
+  legend = legend[0];
+  assertNotEquals(-1, legend.textContent.indexOf("Y1"));
+  assertNotEquals(-1, legend.textContent.indexOf("Y2"));
+};
index bf1e897..b010407 100644 (file)
       function() { return x; }
     </code>
 
+    Functions can return strings, arrays, data tables, URLs, or any other data type.
+
     <a name="datatable"><h3>DataTable</h3>
     <p>You can also specify a Google Visualization Library <a
       href="http://code.google.com/apis/visualization/documentation/reference.html#DataTable">DataTable</a>
index 9254c17..bf5b5fa 100644 (file)
@@ -2,6 +2,8 @@
 <html>
   <head>
     <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
     <title>dygraphs JavaScript Visualization Library</title>
     <!--[if IE]>
       <script type="text/javascript" src="excanvas.js"></script>
         <li><a href="mailto:dygraphs-users [at] googlegroups.com">Contact</a></li>
       </ul>
 
-      <h2>Gallery</h2>
+      <h2>Demos/Usage</h2>
       <ul>
+        <li><a href="#gallery">Usage Gallery</a></li>
+        <hr/>
         <li><a href="tests/">(browse demos)</a></li>
         <li><a href="tests/demo.html">Basic Demo</a></li>
         <li><a href="tests/gviz.html">GViz Demo</a></li>
@@ -91,7 +95,7 @@
       <h1>dygraphs JavaScript Visualization Library</h1>
 
       <p><a href="http://github.com/danvk/dygraphs">http://github.com/danvk/dygraphs</a></p>
-      <p>See <a href="http://blog.dygraphs.com/">blog</a>, <a href="http://groups.google.com/group/dygraphs-users">mailing list</a>, <a href="http://github.com/danvk/dygraphs/downloads/">downloads</a>, <a href="tests/">gallery</a> and <a href="http://code.google.com/p/dygraphs/issues/">open issues</a></p>
+      <p>See <a href="http://blog.dygraphs.com/">blog</a>, <a href="http://groups.google.com/group/dygraphs-users">mailing list</a>, <a href="http://github.com/danvk/dygraphs/downloads/">downloads</a>, <a href="tests/">demos</a> and <a href="http://code.google.com/p/dygraphs/issues/">open issues</a></p>
 
       <p>dygraphs is an open source JavaScript library that produces produces interactive, zoomable charts of time series. It is designed to display dense data sets and enable users to explore and interpret them.</p>
 
 
       <p>dygraphs allows the user to explore the data and discover these facts.</p>
 
-      <p>For more demos, browse the dygraph <a href="tests/">tests</a> directory.</p>
+      <p>For more demos, browse the dygraph <a href="tests/">tests</a>
+      directory. To see other people who are using dygraphs, check out the <a
+      href="#gallery">usage gallery</a>.</p>
 
       <h3>Features</h3>
       <p>Some of the features of dygraphs:</p>
@@ -600,6 +606,57 @@ public static native JavaScriptObject drawDygraph(
 }-*/;
 </pre>
 
+    <a name="gallery" />
+    <h2>Usage Gallery</h2>
+    <p>Since its public release in late 2009, dygraphs has found many users
+    across the web. This is a small collection of the uses that we know about.
+    If you're using dygraphs, please send <a
+    href="mailto:dan@dygraphs.com">Dan</a> a link and he'll add it to this
+    list.</p>
+
+    <p>dygraphs was originally developed at Google and has found wide use on
+    internal dashboards and servers there. There are also a few uses of
+    dygraphs on public Google products:</p>
+
+    <ul class='padded-list'>
+    <li><a href="http://www.google.com/trends/correlate/search?e=id:20xKcnNqHrk&t=weekly">Google Correlate</a><br/>
+    <span class="desc">Uses dygraphs for time series visualization. Mostly a standard configuration, with just a few tweaks to match Google style.</span></li>
+
+    <li><a href="http://www.google.com/trends/correlate/draw?p=us">Google Correlate - Search by Drawing</a><br/>
+    <span class="desc">This is a highly customized configuration which lets the user draw a time series. Based on <a href="tests/drawing.html">this demo</a>.</span></li>
+
+    <li><a href="https://www.google.com/latitude/b/0/history/manage">Google Latitude History Dashboard</a><br/>
+    <span class="desc">Uses mouse interaction callbacks to synchronize time series points with markers on a Google Map.</span></li>
+    </ul>
+
+    <p>dygraphs has also found use in other organizations:</p>
+
+    <ul class='padded-list'>
+    <li><a href="http://toolserver.org/~dartar/moodbar/">Wikimedia Foundation - Moodbar data dashboard</a><br/>
+    <span class="desc">dygraphs is used internally at Wikimedia as a handy solution to monitor the
+    results of a bunch of small experiments.</span></li>
+
+    <li><a href="http://www.socib.es/jwebchart/?file=http://thredds.socib.es/thredds/dodsC/mooring/weather_station/mobims_calamillor-scb_met001/L1/dep0001_mobims-calamillor_scb-met001_L1_latest.nc">Jwebchart</a><br/>
+    <span class="desc">
+    jWebChart is a stand-alone and Thredds' embedded plotting system for
+    netCDF files. NetCDF is a common standard for the storage and
+    distribution of scientific data.
+    </span></li>
+
+    <li><a href="http://ngrams.cavorite.com/">n-gramas - Explore las tendencias en los artículos periodísticos de Colombia.</a><br/>
+
+
+    <span class="desc">(English: "Explore trends in newspaper articles of
+    Colombia"). dygraphs is used for displaying the results of this n-grams
+    viewer.  Uses an extension for exporting the plots as PNG images
+    (<a href="http://cavorite.com/labs/js/dygraphs-export/">[1]</a>, <a href="https://github.com/cavorite/dygraphs">[2]</a>).
+    </span></li>
+
+    </ul>
+
+    <p>Are you using dygraphs? Please let <a href="mailto:dan@dygraphs.com">Dan</a> know and he'll add your link here!</p>
+
+
     <h2 id="policy">Data Policy</h2>
     <p>dygraphs is purely client-side JavaScript. It does not send your data to any servers &ndash; the data is processed entirely in the client's browser.</p>
 
index 23ec7a1..63837bf 100644 (file)
@@ -65,3 +65,11 @@ padding: 0 10px;
   font-weight: normal;
   text-decoration: none;
 }
+
+.padded-list li + li {
+  padding-top: 10px;
+}
+
+.padded-list .desc {
+  font-style: italic;
+}
index 5b2cac8..5e6766d 100644 (file)
  * @param {Layout} layout The DygraphLayout object for this graph.
  * @constructor
  */
-DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
+
+/*jshint globalstrict: true */
+/*global Dygraph:false,RGBColor:false */
+"use strict";
+
+var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
   this.dygraph_ = dygraph;
 
   this.layout = layout;
@@ -39,9 +44,9 @@ DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
       throw "Canvas is not supported.";
 
   // internal state
-  this.xlabels = new Array();
-  this.ylabels = new Array();
-  this.annotations = new Array();
+  this.xlabels = [];
+  this.ylabels = [];
+  this.annotations = [];
   this.chartLabels = {};
 
   this.area = layout.getPlotArea();
@@ -53,15 +58,19 @@ DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
   if (this.dygraph_.isUsingExcanvas_) {
     this._createIEClipArea();
   } else {
-    var ctx = this.dygraph_.canvas_ctx_;
-    ctx.beginPath();
-    ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
-    ctx.clip();
+    // on Android 3 and 4, setting a clipping area on a canvas prevents it from
+    // displaying anything.
+    if (!Dygraph.isAndroid()) {
+      var ctx = this.dygraph_.canvas_ctx_;
+      ctx.beginPath();
+      ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
+      ctx.clip();
 
-    ctx = this.dygraph_.hidden_ctx_;
-    ctx.beginPath();
-    ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
-    ctx.clip();
+      ctx = this.dygraph_.hidden_ctx_;
+      ctx.beginPath();
+      ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
+      ctx.clip();
+    }
   }
 };
 
@@ -70,6 +79,7 @@ DygraphCanvasRenderer.prototype.attr_ = function(x) {
 };
 
 DygraphCanvasRenderer.prototype.clear = function() {
+  var context;
   if (this.isIE) {
     // VML takes a while to start up, so we just poll every this.IEDelay
     try {
@@ -77,39 +87,38 @@ DygraphCanvasRenderer.prototype.clear = function() {
         this.clearDelay.cancel();
         this.clearDelay = null;
       }
-      var context = this.elementContext;
+      context = this.elementContext;
     }
     catch (e) {
       // TODO(danvk): this is broken, since MochiKit.Async is gone.
-      this.clearDelay = MochiKit.Async.wait(this.IEDelay);
-      this.clearDelay.addCallback(bind(this.clear, this));
+      // this.clearDelay = MochiKit.Async.wait(this.IEDelay);
+      // this.clearDelay.addCallback(bind(this.clear, this));
       return;
     }
   }
 
-  var context = this.elementContext;
+  context = this.elementContext;
   context.clearRect(0, 0, this.width, this.height);
 
-  for (var i = 0; i < this.xlabels.length; i++) {
-    var el = this.xlabels[i];
-    if (el.parentNode) el.parentNode.removeChild(el);
-  }
-  for (var i = 0; i < this.ylabels.length; i++) {
-    var el = this.ylabels[i];
-    if (el.parentNode) el.parentNode.removeChild(el);
-  }
-  for (var i = 0; i < this.annotations.length; i++) {
-    var el = this.annotations[i];
-    if (el.parentNode) el.parentNode.removeChild(el);
+  function removeArray(ary) {
+    for (var i = 0; i < ary.length; i++) {
+      var el = ary[i];
+      if (el.parentNode) el.parentNode.removeChild(el);
+    }
   }
+
+  removeArray(this.xlabels);
+  removeArray(this.ylabels);
+  removeArray(this.annotations);
+
   for (var k in this.chartLabels) {
     if (!this.chartLabels.hasOwnProperty(k)) continue;
     var el = this.chartLabels[k];
     if (el.parentNode) el.parentNode.removeChild(el);
   }
-  this.xlabels = new Array();
-  this.ylabels = new Array();
-  this.annotations = new Array();
+  this.xlabels = [];
+  this.ylabels = [];
+  this.annotations = [];
   this.chartLabels = {};
 };
 
@@ -117,11 +126,12 @@ DygraphCanvasRenderer.prototype.clear = function() {
 DygraphCanvasRenderer.isSupported = function(canvasName) {
   var canvas = null;
   try {
-    if (typeof(canvasName) == 'undefined' || canvasName == null)
+    if (typeof(canvasName) == 'undefined' || canvasName === null) {
       canvas = document.createElement("canvas");
-    else
+    } else {
       canvas = canvasName;
-    var context = canvas.getContext("2d");
+    }
+    canvas.getContext("2d");
   }
   catch (e) {
     var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
@@ -148,8 +158,8 @@ DygraphCanvasRenderer.prototype.render = function() {
   // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to
   // half-integers. This prevents them from drawing in two rows/cols.
   var ctx = this.elementContext;
-  function halfUp(x){return Math.round(x)+0.5};
-  function halfDown(y){return Math.round(y)-0.5};
+  function halfUp(x)  { return Math.round(x) + 0.5; }
+  function halfDown(y){ return Math.round(y) - 0.5; }
 
   if (this.attr_('underlayCallback')) {
     // NOTE: we pass the dygraph object to this callback twice to avoid breaking
@@ -157,17 +167,18 @@ DygraphCanvasRenderer.prototype.render = function() {
     this.attr_('underlayCallback')(ctx, this.area, this.dygraph_, this.dygraph_);
   }
 
+  var x, y, i, ticks;
   if (this.attr_('drawYGrid')) {
-    var ticks = this.layout.yticks;
+    ticks = this.layout.yticks;
     // TODO(konigsberg): I don't think these calls to save() have a corresponding restore().
     ctx.save();
     ctx.strokeStyle = this.attr_('gridLineColor');
     ctx.lineWidth = this.attr_('gridLineWidth');
-    for (var i = 0; i < ticks.length; i++) {
+    for (i = 0; i < ticks.length; i++) {
       // TODO(danvk): allow secondary axes to draw a grid, too.
-      if (ticks[i][0] != 0) continue;
-      var x = halfUp(this.area.x);
-      var y = halfDown(this.area.y + ticks[i][1] * this.area.h);
+      if (ticks[i][0] !== 0) continue;
+      x = halfUp(this.area.x);
+      y = halfDown(this.area.y + ticks[i][1] * this.area.h);
       ctx.beginPath();
       ctx.moveTo(x, y);
       ctx.lineTo(x + this.area.w, y);
@@ -177,13 +188,13 @@ DygraphCanvasRenderer.prototype.render = function() {
   }
 
   if (this.attr_('drawXGrid')) {
-    var ticks = this.layout.xticks;
+    ticks = this.layout.xticks;
     ctx.save();
     ctx.strokeStyle = this.attr_('gridLineColor');
     ctx.lineWidth = this.attr_('gridLineWidth');
-    for (var i=0; i<ticks.length; i++) {
-      var x = halfUp(this.area.x + ticks[i][0] * this.area.w);
-      var y = halfDown(this.area.y + this.area.h);
+    for (i=0; i<ticks.length; i++) {
+      x = halfUp(this.area.x + ticks[i][0] * this.area.w);
+      y = halfDown(this.area.y + this.area.h);
       ctx.beginPath();
       ctx.moveTo(x, y);
       ctx.lineTo(x, this.area.y);
@@ -223,7 +234,7 @@ DygraphCanvasRenderer.prototype._createIEClipArea = function() {
   }
 
   function createClipDiv(area) {
-    if (area.w == 0 || area.h == 0) {
+    if (area.w === 0 || area.h === 0) {
       return;
     }
     var elem = document.createElement('div');
@@ -239,24 +250,46 @@ DygraphCanvasRenderer.prototype._createIEClipArea = function() {
 
   var plotArea = this.area;
   // Left side
-  createClipDiv({x:0, y:0, w:plotArea.x, h:this.height});
+  createClipDiv({
+    x:0, y:0,
+    w:plotArea.x,
+    h:this.height
+  });
+
   // Top
-  createClipDiv({x:plotArea.x, y:0, w:this.width-plotArea.x, h:plotArea.y});
+  createClipDiv({
+    x: plotArea.x, y: 0,
+    w: this.width - plotArea.x,
+    h: plotArea.y
+  });
+
   // Right side
-  createClipDiv({x:plotArea.x+plotArea.w, y:0, w:this.width-plotArea.x-plotArea.w, h:this.height});
+  createClipDiv({
+    x: plotArea.x + plotArea.w, y: 0,
+    w: this.width-plotArea.x - plotArea.w,
+    h: this.height
+  });
+
   // Bottom
-  createClipDiv({x:plotArea.x, y:plotArea.y+plotArea.h, w:this.width-plotArea.x, h:this.height-plotArea.h-plotArea.y});
-}
+  createClipDiv({
+    x: plotArea.x,
+    y: plotArea.y + plotArea.h,
+    w: this.width - plotArea.x,
+    h: this.height - plotArea.h - plotArea.y
+  });
+};
 
 DygraphCanvasRenderer.prototype._renderAxis = function() {
   if (!this.attr_('drawXAxis') && !this.attr_('drawYAxis')) return;
 
   // Round pixels to half-integer boundaries for crisper drawing.
-  function halfUp(x){return Math.round(x)+0.5};
-  function halfDown(y){return Math.round(y)-0.5};
+  function halfUp(x)  { return Math.round(x) + 0.5; }
+  function halfDown(y){ return Math.round(y) - 0.5; }
 
   var context = this.elementContext;
 
+  var label, x, y, tick, i;
+
   var labelStyle = {
     position: "absolute",
     fontSize: this.attr_('axisLabelFontSize') + "px",
@@ -264,7 +297,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
     color: this.attr_('axisLabelColor'),
     width: this.attr_('axisLabelWidth') + "px",
     // height: this.attr_('axisLabelFontSize') + 2 + "px",
-    lineHeight: "normal", // Something other than "normal" line-height screws up label positioning.
+    lineHeight: "normal",  // Something other than "normal" line-height screws up label positioning.
     overflow: "hidden"
   };
   var makeDiv = function(txt, axis, prec_axis) {
@@ -291,10 +324,10 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
   if (this.attr_('drawYAxis')) {
     if (this.layout.yticks && this.layout.yticks.length > 0) {
       var num_axes = this.dygraph_.numAxes();
-      for (var i = 0; i < this.layout.yticks.length; i++) {
-        var tick = this.layout.yticks[i];
+      for (i = 0; i < this.layout.yticks.length; i++) {
+        tick = this.layout.yticks[i];
         if (typeof(tick) == "function") return;
-        var x = this.area.x;
+        x = this.area.x;
         var sgn = 1;
         var prec_axis = 'y1';
         if (tick[0] == 1) {  // right-side y-axis
@@ -302,7 +335,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
           sgn = -1;
           prec_axis = 'y2';
         }
-        var y = this.area.y + tick[1] * this.area.h;
+        y = this.area.y + tick[1] * this.area.h;
 
         /* Tick marks are currently clipped, so don't bother drawing them.
         context.beginPath();
@@ -312,7 +345,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
         context.stroke();
         */
 
-        var label = makeDiv(tick[2], 'y', num_axes == 2 ? prec_axis : null);
+        label = makeDiv(tick[2], 'y', num_axes == 2 ? prec_axis : null);
         var top = (y - this.attr_('axisLabelFontSize') / 2);
         if (top < 0) top = 0;
 
@@ -321,7 +354,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
         } else {
           label.style.top = top + "px";
         }
-        if (tick[0] == 0) {
+        if (tick[0] === 0) {
           label.style.left = (this.area.x - this.attr_('yAxisLabelWidth') - this.attr_('axisTickSize')) + "px";
           label.style.textAlign = "right";
         } else if (tick[0] == 1) {
@@ -339,9 +372,9 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
       // compensate if necessary.
       var bottomTick = this.ylabels[0];
       var fontSize = this.attr_('axisLabelFontSize');
-      var bottom = parseInt(bottomTick.style.top) + fontSize;
+      var bottom = parseInt(bottomTick.style.top, 10) + fontSize;
       if (bottom > this.height - fontSize) {
-        bottomTick.style.top = (parseInt(bottomTick.style.top) -
+        bottomTick.style.top = (parseInt(bottomTick.style.top, 10) -
             fontSize / 2) + "px";
       }
     }
@@ -365,12 +398,10 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
 
   if (this.attr_('drawXAxis')) {
     if (this.layout.xticks) {
-      for (var i = 0; i < this.layout.xticks.length; i++) {
-        var tick = this.layout.xticks[i];
-        if (typeof(dataset) == "function") return;
-
-        var x = this.area.x + tick[0] * this.area.w;
-        var y = this.area.y + this.area.h;
+      for (i = 0; i < this.layout.xticks.length; i++) {
+        tick = this.layout.xticks[i];
+        x = this.area.x + tick[0] * this.area.w;
+        y = this.area.y + this.area.h;
 
         /* Tick marks are currently clipped, so don't bother drawing them.
         context.beginPath();
@@ -380,7 +411,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
         context.stroke();
         */
 
-        var label = makeDiv(tick[1], 'x');
+        label = makeDiv(tick[1], 'x');
         label.style.textAlign = "center";
         label.style.top = (y + this.attr_('axisTickSize')) + 'px';
 
@@ -413,11 +444,13 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
 
 
 DygraphCanvasRenderer.prototype._renderChartLabels = function() {
+  var div, class_div;
+
   // Generate divs for the chart title, xlabel and ylabel.
   // Space for these divs has already been taken away from the charting area in
   // the DygraphCanvasRenderer constructor.
   if (this.attr_('title')) {
-    var div = document.createElement("div");
+    div = document.createElement("div");
     div.style.position = 'absolute';
     div.style.top = '0px';
     div.style.left = this.area.x + 'px';
@@ -426,7 +459,7 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() {
     div.style.textAlign = 'center';
     div.style.fontSize = (this.attr_('titleHeight') - 8) + 'px';
     div.style.fontWeight = 'bold';
-    var class_div = document.createElement("div");
+    class_div = document.createElement("div");
     class_div.className = 'dygraph-label dygraph-title';
     class_div.innerHTML = this.attr_('title');
     div.appendChild(class_div);
@@ -435,7 +468,7 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() {
   }
 
   if (this.attr_('xlabel')) {
-    var div = document.createElement("div");
+    div = document.createElement("div");
     div.style.position = 'absolute';
     div.style.bottom = 0;  // TODO(danvk): this is lazy. Calculate style.top.
     div.style.left = this.area.x + 'px';
@@ -444,7 +477,7 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() {
     div.style.textAlign = 'center';
     div.style.fontSize = (this.attr_('xLabelHeight') - 2) + 'px';
 
-    var class_div = document.createElement("div");
+    class_div = document.createElement("div");
     class_div.className = 'dygraph-label dygraph-xlabel';
     class_div.innerHTML = this.attr_('xlabel');
     div.appendChild(class_div);
@@ -452,21 +485,26 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() {
     this.chartLabels.xlabel = div;
   }
 
-  if (this.attr_('ylabel')) {
+  var that = this;
+  function createRotatedDiv(axis, classes, html) {
     var box = {
       left: 0,
-      top: this.area.y,
-      width: this.attr_('yLabelWidth'),
-      height: this.area.h
+      top: that.area.y,
+      width: that.attr_('yLabelWidth'),
+      height: that.area.h
     };
     // TODO(danvk): is this outer div actually necessary?
-    var div = document.createElement("div");
+    div = document.createElement("div");
     div.style.position = 'absolute';
-    div.style.left = box.left;
+    if (axis == 1) {
+      div.style.left = box.left;
+    } else {
+      div.style.right = box.left;
+    }
     div.style.top = box.top + 'px';
     div.style.width = box.width + 'px';
     div.style.height = box.height + 'px';
-    div.style.fontSize = (this.attr_('yLabelWidth') - 2) + 'px';
+    div.style.fontSize = (that.attr_('yLabelWidth') - 2) + 'px';
 
     var inner_div = document.createElement("div");
     inner_div.style.position = 'absolute';
@@ -478,11 +516,12 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() {
 
     // CSS rotation is an HTML5 feature which is not standardized. Hence every
     // browser has its own name for the CSS style.
-    inner_div.style.transform = 'rotate(-90deg)';        // HTML5
-    inner_div.style.WebkitTransform = 'rotate(-90deg)';  // Safari/Chrome
-    inner_div.style.MozTransform = 'rotate(-90deg)';     // Firefox
-    inner_div.style.OTransform = 'rotate(-90deg)';       // Opera
-    inner_div.style.msTransform = 'rotate(-90deg)';      // IE9
+    var val = 'rotate(' + (axis == 1 ? '-' : '') + '90deg)';
+    inner_div.style.transform = val;        // HTML5
+    inner_div.style.WebkitTransform = val;  // Safari/Chrome
+    inner_div.style.MozTransform = val;     // Firefox
+    inner_div.style.OTransform = val;       // Opera
+    inner_div.style.msTransform = val;      // IE9
 
     if (typeof(document.documentMode) !== 'undefined' &&
         document.documentMode < 9) {
@@ -490,20 +529,34 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() {
       // using a BasicImage transform. This uses a different origin of rotation
       // than HTML5 rotation (top left of div vs. its center).
       inner_div.style.filter =
-       'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
+          'progid:DXImageTransform.Microsoft.BasicImage(rotation=' +
+          (axis == 1 ? '3' : '1') + ')';
       inner_div.style.left = '0px';
       inner_div.style.top = '0px';
     }
 
-    var class_div = document.createElement("div");
-    class_div.className = 'dygraph-label dygraph-ylabel';
-    class_div.innerHTML = this.attr_('ylabel');
+    class_div = document.createElement("div");
+    class_div.className = classes;
+    class_div.innerHTML = html;
 
     inner_div.appendChild(class_div);
     div.appendChild(inner_div);
+    return div;
+  }
+
+  var div;
+  if (this.attr_('ylabel')) {
+    div = createRotatedDiv(1, 'dygraph-label dygraph-ylabel',
+                           this.attr_('ylabel'));
     this.container.appendChild(div);
     this.chartLabels.ylabel = div;
   }
+  if (this.attr_('y2label') && this.dygraph_.numAxes() == 2) {
+    div = createRotatedDiv(2, 'dygraph-label dygraph-y2label',
+                           this.attr_('y2label'));
+    this.container.appendChild(div);
+    this.chartLabels.y2label = div;
+  }
 };
 
 
@@ -524,7 +577,7 @@ DygraphCanvasRenderer.prototype._renderAnnotations = function() {
         self.dygraph_.attr_(classEventName)(a, p, self.dygraph_,e );
       }
     };
-  }
+  };
 
   // Get a list of point with annotations.
   var points = this.layout.annotated_points;
@@ -606,7 +659,9 @@ DygraphCanvasRenderer.prototype._renderAnnotations = function() {
 
 
 /**
- * Overrides the CanvasRenderer method to draw error bars
+ * Actually draw the lines chart, including error bars.
+ * TODO(danvk): split this into several smaller functions.
+ * @private
  */
 DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var isNullOrNaN = function(x) {
@@ -622,6 +677,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var stepPlot = this.attr_("stepPlot");
   var points = this.layout.points;
   var pointsLength = points.length;
+  var point, i, j, prevX, prevY, prevYs, color, setName, newYs, err_color, rgb, yscale, axis;
 
   var setNames = [];
   for (var name in this.layout.datasets) {
@@ -632,15 +688,15 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var setCount = setNames.length;
 
   // TODO(danvk): Move this mapping into Dygraph and get it out of here.
-  this.colors = {}
-  for (var i = 0; i < setCount; i++) {
+  this.colors = {};
+  for (i = 0; i < setCount; i++) {
     this.colors[setNames[i]] = this.colorScheme_[i % this.colorScheme_.length];
   }
 
   // Update Points
   // TODO(danvk): here
-  for (var i = pointsLength; i--;) {
-    var point = points[i];
+  for (i = pointsLength; i--;) {
+    point = points[i];
     point.canvasx = this.area.w * point.x + this.area.x;
     point.canvasy = this.area.h * point.y + this.area.y;
   }
@@ -652,25 +708,25 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       this.dygraph_.warn("Can't use fillGraph option with error bars");
     }
 
-    for (var i = 0; i < setCount; i++) {
-      var setName = setNames[i];
-      var axis = this.dygraph_.axisPropertiesForSeries(setName);
-      var color = this.colors[setName];
+    for (i = 0; i < setCount; i++) {
+      setName = setNames[i];
+      axis = this.dygraph_.axisPropertiesForSeries(setName);
+      color = this.colors[setName];
 
       // setup graphics context
       ctx.save();
-      var prevX = NaN;
-      var prevY = NaN;
-      var prevYs = [-1, -1];
-      var yscale = axis.yscale;
+      prevX = NaN;
+      prevY = NaN;
+      prevYs = [-1, -1];
+      yscale = axis.yscale;
       // should be same color as the lines but only 15% opaque.
-      var rgb = new RGBColor(color);
-      var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
+      rgb = new RGBColor(color);
+      err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
                             fillAlpha + ')';
       ctx.fillStyle = err_color;
       ctx.beginPath();
-      for (var j = 0; j < pointsLength; j++) {
-        var point = points[j];
+      for (j = 0; j < pointsLength; j++) {
+        point = points[j];
         if (point.name == setName) {
           if (!Dygraph.isOK(point.y)) {
             prevX = NaN;
@@ -679,10 +735,10 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
 
           // TODO(danvk): here
           if (stepPlot) {
-            var newYs = [ point.y_bottom, point.y_top ];
+            newYs = [ point.y_bottom, point.y_top ];
             prevY = point.y;
           } else {
-            var newYs = [ point.y_bottom, point.y_top ];
+            newYs = [ point.y_bottom, point.y_top ];
           }
           newYs[0] = this.area.h * newYs[0] + this.area.y;
           newYs[1] = this.area.h * newYs[1] + this.area.y;
@@ -708,13 +764,13 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       ctx.fill();
     }
   } else if (fillGraph) {
-    var baseline = []  // for stacked graphs: baseline for filling
+    var baseline = [];  // for stacked graphs: baseline for filling
 
     // process sets in reverse order (needed for stacked graphs)
-    for (var i = setCount - 1; i >= 0; i--) {
-      var setName = setNames[i];
-      var color = this.colors[setName];
-      var axis = this.dygraph_.axisPropertiesForSeries(setName);
+    for (i = setCount - 1; i >= 0; i--) {
+      setName = setNames[i];
+      color = this.colors[setName];
+      axis = this.dygraph_.axisPropertiesForSeries(setName);
       var axisY = 1.0 + axis.minyval * axis.yscale;
       if (axisY < 0.0) axisY = 0.0;
       else if (axisY > 1.0) axisY = 1.0;
@@ -722,25 +778,24 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
 
       // setup graphics context
       ctx.save();
-      var prevX = NaN;
-      var prevYs = [-1, -1];
-      var yscale = axis.yscale;
+      prevX = NaN;
+      prevYs = [-1, -1];
+      yscale = axis.yscale;
       // should be same color as the lines but only 15% opaque.
-      var rgb = new RGBColor(color);
-      var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
+      rgb = new RGBColor(color);
+      err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
                             fillAlpha + ')';
       ctx.fillStyle = err_color;
       ctx.beginPath();
-      for (var j = 0; j < pointsLength; j++) {
-        var point = points[j];
+      for (j = 0; j < pointsLength; j++) {
+        point = points[j];
         if (point.name == setName) {
           if (!Dygraph.isOK(point.y)) {
             prevX = NaN;
             continue;
           }
-          var newYs;
           if (stackedGraph) {
-            lastY = baseline[point.canvasx];
+            var lastY = baseline[point.canvasx];
             if (lastY === undefined) lastY = axisY;
             baseline[point.canvasx] = point.canvasy;
             newYs = [ point.canvasy, lastY ];
@@ -770,22 +825,23 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var firstIndexInSet = 0;
   var afterLastIndexInSet = 0;
   var setLength = 0;
-  for (var i = 0; i < setCount; i += 1) {
+  for (i = 0; i < setCount; i += 1) {
     setLength = this.layout.setPointsLengths[i];
     afterLastIndexInSet += setLength;
-    var setName = setNames[i];
-    var color = this.colors[setName];
+    setName = setNames[i];
+    color = this.colors[setName];
     var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
 
     // setup graphics context
     context.save();
     var pointSize = this.dygraph_.attr_("pointSize", setName);
-    var prevX = null, prevY = null;
+    prevX = null;
+    prevY = null;
     var drawPoints = this.dygraph_.attr_("drawPoints", setName);
-    for (var j = firstIndexInSet; j < afterLastIndexInSet; j++) {
-      var point = points[j];
+    for (j = firstIndexInSet; j < afterLastIndexInSet; j++) {
+      point = points[j];
       if (isNullOrNaN(point.canvasy)) {
-        if (stepPlot && prevX != null) {
+        if (stepPlot && prevX !== null) {
           // Draw a horizontal line to the start of the missing data
           ctx.beginPath();
           ctx.strokeStyle = color;
index 1f6005a..f17ee2b 100644 (file)
@@ -18,6 +18,7 @@
   var source_files = [
     "strftime/strftime-min.js",
     "rgbcolor/rgbcolor.js",
+    "stacktrace.js",
     "dygraph-layout.js",
     "dygraph-canvas.js",
     "dygraph.js",
index 5b81d7f..b87fe80 100644 (file)
  * - http://dygraphs.com/tests/annotation-gviz.html
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+"use strict";
+
 /**
  * A wrapper around Dygraph that implements the gviz API.
  * @param {Object} container The DOM object the visualization should live in.
  */
 Dygraph.GVizChart = function(container) {
   this.container = container;
-}
+};
 
 Dygraph.GVizChart.prototype.draw = function(data, options) {
   // Clear out any existing dygraph.
@@ -35,7 +39,7 @@ Dygraph.GVizChart.prototype.draw = function(data, options) {
   }
 
   this.date_graph = new Dygraph(this.container, data, options);
-}
+};
 
 /**
  * Google charts compatible setSelection
@@ -49,7 +53,7 @@ Dygraph.GVizChart.prototype.setSelection = function(selection_array) {
     row = selection_array[0].row;
   }
   this.date_graph.setSelection(row);
-}
+};
 
 /**
  * Google charts compatible getSelection implementation
@@ -63,12 +67,14 @@ Dygraph.GVizChart.prototype.getSelection = function() {
 
   if (row < 0) return selection;
 
-  col = 1;
-  for (var i in this.date_graph.layout_.datasets) {
+  var col = 1;
+  var datasets = this.date_graph.layout_.datasets;
+  for (var k in datasets) {
+    if (!datasets.hasOwnProperty(k)) continue;
     selection.push({row: row, column: col});
     col++;
   }
 
   return selection;
-}
+};
 
index 98d1411..55184ec 100644 (file)
@@ -10,6 +10,9 @@
  * @author Robert Konigsberg (konigsberg@google.com)
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+"use strict";
 
 /**
  * A collection of functions to facilitate build custom interaction models.
@@ -31,6 +34,7 @@ Dygraph.Interaction = {};
  * dragStartX/dragStartY/etc. properties). This function modifies the context.
  */
 Dygraph.Interaction.startPan = function(event, g, context) {
+  var i, axis;
   context.isPanning = true;
   var xRange = g.xAxisRange();
   context.dateRange = xRange[1] - xRange[0];
@@ -51,8 +55,8 @@ Dygraph.Interaction.startPan = function(event, g, context) {
     var boundedValues = [];
     var maxYPixelsToDraw = g.height_ * g.attr_("panEdgeFraction");
 
-    for (var i = 0; i < g.axes_.length; i++) {
-      var axis = g.axes_[i];
+    for (i = 0; i < g.axes_.length; i++) {
+      axis = g.axes_[i];
       var yExtremes = axis.extremeRange;
 
       var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw;
@@ -69,8 +73,8 @@ Dygraph.Interaction.startPan = function(event, g, context) {
   // Record the range of each y-axis at the start of the drag.
   // If any axis has a valueRange or valueWindow, then we want a 2D pan.
   context.is2DPan = false;
-  for (var i = 0; i < g.axes_.length; i++) {
-    var axis = g.axes_[i];
+  for (i = 0; i < g.axes_.length; i++) {
+    axis = g.axes_[i];
     var yRange = g.yAxisRange(i);
     // TODO(konigsberg): These values should be in |context|.
     // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale.
@@ -178,7 +182,7 @@ Dygraph.Interaction.endPan = function(event, g, context) {
   var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
 
   if (regionWidth < 2 && regionHeight < 2 &&
-      g.lastx_ != undefined && g.lastx_ != -1) {
+      g.lastx_ !== undefined && g.lastx_ != -1) {
     Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
   }
 
@@ -312,7 +316,7 @@ Dygraph.Interaction.endZoom = function(event, g, context) {
   var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
 
   if (regionWidth < 2 && regionHeight < 2 &&
-      g.lastx_ != undefined && g.lastx_ != -1) {
+      g.lastx_ !== undefined && g.lastx_ != -1) {
     Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
   }
 
@@ -409,7 +413,7 @@ Dygraph.Interaction.nonInteractiveModel_ = {
     var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
 
     if (regionWidth < 2 && regionHeight < 2 &&
-        g.lastx_ != undefined && g.lastx_ != -1) {
+        g.lastx_ !== undefined && g.lastx_ != -1) {
       Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
     }
   }
index 5fd6e52..328da81 100644 (file)
@@ -9,6 +9,10 @@
  * dygraphs.
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+"use strict";
+
 /**
  * Creates a new DygraphLayout object.
  *
  *
  * @constructor
  */
-DygraphLayout = function(dygraph) {
+var DygraphLayout = function(dygraph) {
   this.dygraph_ = dygraph;
-  this.datasets = new Array();
-  this.annotations = new Array();
+  this.datasets = [];
+  this.annotations = [];
   this.yAxes_ = null;
 
   // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and
@@ -47,7 +51,7 @@ DygraphLayout.prototype.addDataset = function(setname, set_xy) {
 
 DygraphLayout.prototype.getPlotArea = function() {
   return this.computePlotArea_();
-}
+};
 
 // Compute the box which the chart should be drawn in. This is the canvas's
 // box, less space needed for axis and chart labels.
@@ -95,6 +99,11 @@ DygraphLayout.prototype.computePlotArea_ = function() {
     // doesn't, the yAxisLabelWidth option can be increased.
   }
 
+  if (this.attr_('y2label')) {
+    // same logic applies here as for ylabel.
+    // TODO(danvk): make yAxisLabelWidth a per-axis property
+  }
+
   // Add space for range selector, if needed.
   if (this.attr_('showRangeSelector')) {
     area.h -= this.attr_('rangeSelectorHeight') + 4;
@@ -166,18 +175,18 @@ DygraphLayout.prototype._evaluateLimits = function() {
     }
   }
   this.xrange = this.maxxval - this.minxval;
-  this.xscale = (this.xrange != 0 ? 1/this.xrange : 1.0);
+  this.xscale = (this.xrange !== 0 ? 1/this.xrange : 1.0);
 
   for (var i = 0; i < this.yAxes_.length; i++) {
     var axis = this.yAxes_[i];
     axis.minyval = axis.computedValueRange[0];
     axis.maxyval = axis.computedValueRange[1];
     axis.yrange = axis.maxyval - axis.minyval;
-    axis.yscale = (axis.yrange != 0 ? 1.0 / axis.yrange : 1.0);
+    axis.yscale = (axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0);
 
     if (axis.g.attr_("logscale")) {
       axis.ylogrange = Dygraph.log10(axis.maxyval) - Dygraph.log10(axis.minyval);
-      axis.ylogscale = (axis.ylogrange != 0 ? 1.0 / axis.ylogrange : 1.0);
+      axis.ylogscale = (axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0);
       if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) {
         axis.g.error('axis ' + i + ' of graph at ' + axis.g +
             ' can\'t be displayed in log scale for range [' +
@@ -197,12 +206,12 @@ DygraphLayout._calcYNormal = function(axis, value) {
 
 DygraphLayout.prototype._evaluateLineCharts = function() {
   // add all the rects
-  this.points = new Array();
+  this.points = [];
   // An array to keep track of how many points will be drawn for each set.
   // This will allow for the canvas renderer to not have to check every point
   // for every data set since the points are added in order of the sets in
   // datasets.
-  this.setPointsLengths = new Array();
+  this.setPointsLengths = [];
 
   for (var setName in this.datasets) {
     if (!this.datasets.hasOwnProperty(setName)) continue;
@@ -214,8 +223,8 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
 
     for (var j = 0; j < dataset.length; j++) {
       var item = dataset[j];
-      var xValue = parseFloat(dataset[j][0]);
-      var yValue = parseFloat(dataset[j][1]);
+      var xValue = parseFloat(item[0]);
+      var yValue = parseFloat(item[1]);
 
       // Range from 0-1 where 0 represents left and 1 represents right.
       var xNormal = (xValue - this.minxval) * this.xscale;
@@ -238,23 +247,24 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
 };
 
 DygraphLayout.prototype._evaluateLineTicks = function() {
-  this.xticks = new Array();
-  for (var i = 0; i < this.xTicks_.length; i++) {
-    var tick = this.xTicks_[i];
-    var label = tick.label;
-    var pos = this.xscale * (tick.v - this.minxval);
+  var i, tick, label, pos;
+  this.xticks = [];
+  for (i = 0; i < this.xTicks_.length; i++) {
+    tick = this.xTicks_[i];
+    label = tick.label;
+    pos = this.xscale * (tick.v - this.minxval);
     if ((pos >= 0.0) && (pos <= 1.0)) {
       this.xticks.push([pos, label]);
     }
   }
 
-  this.yticks = new Array();
-  for (var i = 0; i < this.yAxes_.length; i++ ) {
+  this.yticks = [];
+  for (i = 0; i < this.yAxes_.length; i++ ) {
     var axis = this.yAxes_[i];
     for (var j = 0; j < axis.ticks.length; j++) {
-      var tick = axis.ticks[j];
-      var label = tick.label;
-      var pos = this.dygraph_.toPercentYCoord(tick.v, i);
+      tick = axis.ticks[j];
+      label = tick.label;
+      pos = this.dygraph_.toPercentYCoord(tick.v, i);
       if ((pos >= 0.0) && (pos <= 1.0)) {
         this.yticks.push([i, pos, label]);
       }
@@ -272,13 +282,13 @@ DygraphLayout.prototype.evaluateWithError = function() {
   if (!(this.attr_('errorBars') || this.attr_('customBars'))) return;
 
   // Copy over the error terms
-  var i = 0; // index in this.points
+  var i = 0;  // index in this.points
   for (var setName in this.datasets) {
     if (!this.datasets.hasOwnProperty(setName)) continue;
     var j = 0;
     var dataset = this.datasets[setName];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
-    for (var j = 0; j < dataset.length; j++, i++) {
+    for (j = 0; j < dataset.length; j++, i++) {
       var item = dataset[j];
       var xv = parseFloat(item[0]);
       var yv = parseFloat(item[1]);
@@ -300,8 +310,9 @@ DygraphLayout.prototype.evaluateWithError = function() {
 DygraphLayout.prototype._evaluateAnnotations = function() {
   // Add the annotations to the point to which they belong.
   // Make a map from (setName, xval) to annotation for quick lookups.
+  var i;
   var annotations = {};
-  for (var i = 0; i < this.annotations.length; i++) {
+  for (i = 0; i < this.annotations.length; i++) {
     var a = this.annotations[i];
     annotations[a.xval + "," + a.series] = a;
   }
@@ -314,7 +325,7 @@ DygraphLayout.prototype._evaluateAnnotations = function() {
   }
 
   // TODO(antrob): loop through annotations not points.
-  for (var i = 0; i < this.points.length; i++) {
+  for (i = 0; i < this.points.length; i++) {
     var p = this.points[i];
     var k = p.xval + "," + p.name;
     if (k in annotations) {
@@ -329,7 +340,7 @@ DygraphLayout.prototype._evaluateAnnotations = function() {
  */
 DygraphLayout.prototype.removeAllDatasets = function() {
   delete this.datasets;
-  this.datasets = new Array();
+  this.datasets = [];
 };
 
 /**
@@ -341,8 +352,8 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
 
   // Clone the point since we modify it
   var unstackedPoint = {};
-  for (var i in point) {
-    unstackedPoint[i] = point[i];
+  for (var pt in point) {
+    unstackedPoint[pt] = point[pt];
   }
 
   if (!this.attr_("stackedGraph")) {
@@ -359,4 +370,4 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
   }
 
   return unstackedPoint;
-}
+};
index b29bb47..92846ee 100644 (file)
@@ -4,6 +4,9 @@
  * MIT-licensed (http://opensource.org/licenses/MIT)
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+
 // NOTE: in addition to parsing as JS, this snippet is expected to be valid
 // JSON. This assumption cannot be checked in JS, but it will be checked when
 // documentation is generated by the generate-documentation.py script. For the
@@ -192,17 +195,11 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "type": "Object",
     "description": "TODO(konigsberg): document this"
   },
-  "xTicker": {
+  "ticker": {
     "default": "Dygraph.dateTicker or Dygraph.numericTicks",
     "labels": ["Axis display"],
     "type": "function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]",
-    "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion."
-  },
-  "xTicker": {
-    "default": "",
-    "labels": ["Deprecated"],
-    "type": "",
-    "description": "Prefer axes: { x: { ticker } }"
+    "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a <a href='per-axis.html'>per-axis</a> basis."
   },
   "xAxisLabelWidth": {
     "default": "50",
@@ -447,7 +444,6 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "null",
     "labels": ["Axis display", "Interactive Elements"],
     "type": "float",
-    "default": "null",
     "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% pased the edges of the displayed values. null means no bounds."
   },
   "title": {
@@ -480,6 +476,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "null",
     "description": "Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option."
   },
+  "y2label": {
+    "labels": ["Chart labels"],
+    "type": "string",
+    "default": "null",
+    "description": "Text to display to the right of the chart's secondary y-axis. This label is only displayed if a secondary y-axis is present. See <a href='http://dygraphs.com/tests/two-axes.html'>this test</a> for an example of how to do this. The comments for the 'ylabel' option generally apply here as well. This label gets a 'dygraph-y2label' instead of a 'dygraph-ylabel' class."
+  },
   "yLabelWidth": {
     "labels": ["Chart labels"],
     "type": "integer",
@@ -621,6 +623,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
 
 // Do a quick sanity check on the options reference.
 (function() {
+  "use strict";
   var warn = function(msg) { if (console) console.warn(msg); };
   var flds = ['type', 'default', 'description'];
   var valid_cats = [
@@ -643,24 +646,25 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
    'Debugging',
    'Deprecated'
   ];
+  var i;
   var cats = {};
-  for (var i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true;
+  for (i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true;
 
   for (var k in Dygraph.OPTIONS_REFERENCE) {
     if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(k)) continue;
     var op = Dygraph.OPTIONS_REFERENCE[k];
-    for (var i = 0; i < flds.length; i++) {
+    for (i = 0; i < flds.length; i++) {
       if (!op.hasOwnProperty(flds[i])) {
         warn('Option ' + k + ' missing "' + flds[i] + '" property');
       } else if (typeof(op[flds[i]]) != 'string') {
         warn(k + '.' + flds[i] + ' must be of type string');
       }
     }
-    var labels = op['labels'];
+    var labels = op.labels;
     if (typeof(labels) !== 'object') {
       warn('Option "' + k + '" is missing a "labels": [...] option');
     } else {
-      for (var i = 0; i < labels.length; i++) {
+      for (i = 0; i < labels.length; i++) {
         if (!cats.hasOwnProperty(labels[i])) {
           warn('Option "' + k + '" has label "' + labels[i] +
                '", which is invalid.');
index 15be96f..fe69864 100644 (file)
@@ -6,12 +6,16 @@
  * a timeline range selector widget for dygraphs.
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+"use strict";
+
 /**
  * The DygraphRangeSelector class provides a timeline range selector widget.
  * @param {Dygraph} dygraph The dygraph object
  * @constructor
  */
-DygraphRangeSelector = function(dygraph) {
+var DygraphRangeSelector = function(dygraph) {
   this.isIE_ = /MSIE/.test(navigator.userAgent) && !window.opera;
   this.isUsingExcanvas_ = dygraph.isUsingExcanvas_;
   this.dygraph_ = dygraph;
@@ -68,7 +72,7 @@ DygraphRangeSelector.prototype.resize_ = function() {
     canvas.height = rect.h;
     canvas.style.width = canvas.width + 'px';    // for IE
     canvas.style.height = canvas.height + 'px';  // for IE
-  };
+  }
 
   var plotArea = this.layout_.getPlotArea();
   var xAxisLabelHeight = this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize');
@@ -139,12 +143,12 @@ DygraphRangeSelector.prototype.createZoomHandles_ = function() {
   } else {
       img.width = 9;
       img.height = 16;
-      img.src = 'data:image/png;base64,\
-iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA\
-zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv\
-bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl\
-6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s\
-qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
+      img.src = 'data:image/png;base64,' +
+'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
+'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
+'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
+'6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
+'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
   }
 
   this.leftZoomHandle_ = img;
@@ -163,7 +167,12 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
   var isZooming = false;
   var isPanning = false;
 
-  function toXDataWindow(zoomHandleStatus) {
+  // functions, defined below.  Defining them this way (rather than with
+  // "function foo() {...}" makes JSHint happy.
+  var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone,
+      onPanStart, onPan, onPanEnd, doPan, onCanvasMouseMove;
+
+  toXDataWindow = function(zoomHandleStatus) {
     var xDataLimits = self.dygraph_.xAxisExtremes();
     var fact = (xDataLimits[1] - xDataLimits[0])/self.canvasRect_.w;
     var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x)*fact;
@@ -171,7 +180,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     return [xDataMin, xDataMax];
   };
 
-  function onZoomStart(e) {
+  onZoomStart = function(e) {
     Dygraph.cancelEvent(e);
     isZooming = true;
     xLast = e.screenX;
@@ -181,7 +190,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     self.fgcanvas_.style.cursor = 'col-resize';
   };
 
-  function onZoom(e) {
+  onZoom = function(e) {
     if (!isZooming) {
       return;
     }
@@ -191,12 +200,13 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
     xLast = e.screenX;
     var zoomHandleStatus = self.getZoomHandleStatus_();
+    var newPos;
     if (handle == self.leftZoomHandle_) {
-      var newPos = zoomHandleStatus.leftHandlePos + delX;
+      newPos = zoomHandleStatus.leftHandlePos + delX;
       newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3);
       newPos = Math.max(newPos, self.canvasRect_.x);
     } else {
-      var newPos = zoomHandleStatus.rightHandlePos + delX;
+      newPos = zoomHandleStatus.rightHandlePos + delX;
       newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w);
       newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3);
     }
@@ -210,7 +220,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function onZoomEnd(e) {
+  onZoomEnd = function(e) {
     if (!isZooming) {
       return;
     }
@@ -225,7 +235,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function doZoom() {
+  doZoom = function() {
     try {
       var zoomHandleStatus = self.getZoomHandleStatus_();
       self.isChangingRange_ = true;
@@ -240,18 +250,18 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function isMouseInPanZone(e) {
+  isMouseInPanZone = function(e) {
     if (self.isUsingExcanvas_) {
         return e.srcElement == self.iePanOverlay_;
     } else {
       // Getting clientX directly from the event is not accurate enough :(
-      var clientX = self.canvasRect_.x + (e.layerX != undefined ? e.layerX : e.offsetX);
+      var clientX = self.canvasRect_.x + (e.layerX !== undefined ? e.layerX : e.offsetX);
       var zoomHandleStatus = self.getZoomHandleStatus_();
       return (clientX > zoomHandleStatus.leftHandlePos && clientX < zoomHandleStatus.rightHandlePos);
     }
   };
 
-  function onPanStart(e) {
+  onPanStart = function(e) {
     if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) {
       Dygraph.cancelEvent(e);
       isPanning = true;
@@ -261,7 +271,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function onPan(e) {
+  onPan = function(e) {
     if (!isPanning) {
       return;
     }
@@ -299,7 +309,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function onPanEnd(e) {
+  onPanEnd = function(e) {
     if (!isPanning) {
       return;
     }
@@ -312,7 +322,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function doPan() {
+  doPan = function() {
     try {
       self.isChangingRange_ = true;
       self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_());
@@ -322,7 +332,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  function onCanvasMouseMove(e) {
+  onCanvasMouseMove = function(e) {
     if (isZooming || isPanning) {
       return;
     }
@@ -350,7 +360,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() {
   };
 
   this.dygraph_.attrs_.interactionModel = interactionModel;
-  this.dygraph_.attrs_.panEdgeFraction = .0001;
+  this.dygraph_.attrs_.panEdgeFraction = 0.0001;
 
   var dragStartEvent = window.opera ? 'mousedown' : 'dragstart';
   Dygraph.addEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart);
@@ -374,9 +384,10 @@ DygraphRangeSelector.prototype.drawStaticLayer_ = function() {
   try {
     this.drawMiniPlot_();
   } catch(ex) {
+    Dygraph.warn(ex);
   }
 
-  var margin = .5;
+  var margin = 0.5;
   this.bgcanvas_ctx_.lineWidth = 1;
   ctx.strokeStyle = 'gray';
   ctx.beginPath();
@@ -404,7 +415,7 @@ DygraphRangeSelector.prototype.drawMiniPlot_ = function() {
 
   // Draw the mini plot.
   var ctx = this.bgcanvas_ctx_;
-  var margin = .5;
+  var margin = 0.5;
 
   var xExtremes = this.dygraph_.xAxisExtremes();
   var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30);
@@ -454,43 +465,51 @@ DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
   var combinedSeries = [];
   var sum;
   var count;
-  var mutipleValues = typeof data[0][1] != 'number';
-
-  if (mutipleValues) {
-    sum = [];
-    count = [];
-    for (var k = 0; k < data[0][1].length; k++) {
-      sum.push(0);
-      count.push(0);
+  var yVal, y;
+  var mutipleValues;
+  var i, j, k;
+
+  // Find out if data has multiple values per datapoint.
+  // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail?id=246)
+  for (i = 0; i < data.length; i++) {
+    if (data[i].length > 1 && data[i][1] != null) {
+      mutipleValues = typeof data[i][1] != 'number';
+      if (mutipleValues) {
+        sum = [];
+        count = [];
+        for (k = 0; k < data[i][1].length; k++) {
+          sum.push(0);
+          count.push(0);
+        }
+      }
+      break;
     }
-    mutipleValues = true;
   }
 
-  for (var i = 0; i < data.length; i++) {
+  for (i = 0; i < data.length; i++) {
     var dataPoint = data[i];
     var xVal = dataPoint[0];
-    var yVal;
 
     if (mutipleValues) {
-      for (var k = 0; k < sum.length; k++) {
+      for (k = 0; k < sum.length; k++) {
         sum[k] = count[k] = 0;
       }
     } else {
       sum = count = 0;
     }
 
-    for (var j = 1; j < dataPoint.length; j++) {
+    for (j = 1; j < dataPoint.length; j++) {
       if (this.dygraph_.visibility()[j-1]) {
         if (mutipleValues) {
-          for (var k = 0; k < sum.length; k++) {
-            var y = dataPoint[j][k];
-            if (y == null || isNaN(y)) continue;
+          for (k = 0; k < sum.length; k++) {
+            y = dataPoint[j][k];
+            if (y === null || isNaN(y)) continue;
             sum[k] += y;
             count[k]++;
           }
         } else {
-          var y = dataPoint[j];
-          if (y == null || isNaN(y)) continue;
+          y = dataPoint[j];
+          if (y === null || isNaN(y)) continue;
           sum += y;
           count++;
         }
@@ -498,7 +517,7 @@ DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
     }
 
     if (mutipleValues) {
-      for (var k = 0; k < sum.length; k++) {
+      for (k = 0; k < sum.length; k++) {
         sum[k] /= count[k];
       }
       yVal = sum.slice(0);
@@ -513,8 +532,8 @@ DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
   combinedSeries = this.dygraph_.rollingAverage(combinedSeries, this.dygraph_.rollPeriod_);
 
   if (typeof combinedSeries[0][1] != 'number') {
-    for (var i = 0; i < combinedSeries.length; i++) {
-      var yVal = combinedSeries[i][1];
+    for (i = 0; i < combinedSeries.length; i++) {
+      yVal = combinedSeries[i][1];
       combinedSeries[i][1] = yVal[0];
     }
   }
@@ -522,9 +541,9 @@ DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
   // Compute the y range.
   var yMin = Number.MAX_VALUE;
   var yMax = -Number.MAX_VALUE;
-  for (var i = 0; i < combinedSeries.length; i++) {
-    var yVal = combinedSeries[i][1];
-    if (yVal != null && isFinite(yVal) && (!logscale || yVal > 0)) {
+  for (i = 0; i < combinedSeries.length; i++) {
+    yVal = combinedSeries[i][1];
+    if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) {
       yMin = Math.min(yMin, yVal);
       yMax = Math.max(yMax, yVal);
     }
@@ -532,17 +551,17 @@ DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
 
   // Convert Y data to log scale if needed.
   // Also, expand the Y range to compress the mini plot a little.
-  var extraPercent = .25;
+  var extraPercent = 0.25;
   if (logscale) {
     yMax = Dygraph.log10(yMax);
     yMax += yMax*extraPercent;
     yMin = Dygraph.log10(yMin);
-    for (var i = 0; i < combinedSeries.length; i++) {
+    for (i = 0; i < combinedSeries.length; i++) {
       combinedSeries[i][1] = Dygraph.log10(combinedSeries[i][1]);
     }
   } else {
     var yExtra;
-    yRange = yMax - yMin;
+    var yRange = yMax - yMin;
     if (yRange <= Number.MIN_VALUE) {
       yExtra = yMax*extraPercent;
     } else {
@@ -602,8 +621,8 @@ DygraphRangeSelector.prototype.drawInteractiveLayer_ = function() {
       this.iePanOverlay_.style.display = 'none';
     }
   } else {
-    leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x);
-    rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x);
+    var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x);
+    var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x);
 
     ctx.fillStyle = 'rgba(240, 240, 240, 0.6)';
     ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h);
@@ -634,8 +653,8 @@ DygraphRangeSelector.prototype.drawInteractiveLayer_ = function() {
  */
 DygraphRangeSelector.prototype.getZoomHandleStatus_ = function() {
   var halfHandleWidth = this.leftZoomHandle_.width/2;
-  var leftHandlePos = parseInt(this.leftZoomHandle_.style.left) + halfHandleWidth;
-  var rightHandlePos = parseInt(this.rightZoomHandle_.style.left) + halfHandleWidth;
+  var leftHandlePos = parseInt(this.leftZoomHandle_.style.left, 10) + halfHandleWidth;
+  var rightHandlePos = parseInt(this.rightZoomHandle_.style.left, 10) + halfHandleWidth;
   return {
       leftHandlePos: leftHandlePos,
       rightHandlePos: rightHandlePos,
index 0ca06e7..64cf59f 100644 (file)
  *   middle of the years.
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+"use strict";
+
 Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
   var pixels_per_tick = opts('pixelsPerLabel');
   var ticks = [];
+  var i, j, tickV, nTicks;
   if (vals) {
-    for (var i = 0; i < vals.length; i++) {
+    for (i = 0; i < vals.length; i++) {
       ticks.push({v: vals[i]});
     }
   } else {
     // TODO(danvk): factor this log-scale block out into a separate function.
     if (opts("logscale")) {
-      var nTicks  = Math.floor(pixels / pixels_per_tick);
+      nTicks  = Math.floor(pixels / pixels_per_tick);
       var minIdx = Dygraph.binarySearch(a, Dygraph.PREFERRED_LOG_TICK_VALUES, 1);
       var maxIdx = Dygraph.binarySearch(b, Dygraph.PREFERRED_LOG_TICK_VALUES, -1);
       if (minIdx == -1) {
@@ -85,7 +90,7 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
           var tickValue = Dygraph.PREFERRED_LOG_TICK_VALUES[idx];
           var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels;
           var tick = { v: tickValue };
-          if (lastDisplayed == null) {
+          if (lastDisplayed === null) {
             lastDisplayed = {
               tickValue : tickValue,
               pixel_coord : pixel_coord
@@ -108,31 +113,34 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
     }
 
     // ticks.length won't be 0 if the log scale function finds values to insert.
-    if (ticks.length == 0) {
+    if (ticks.length === 0) {
       // Basic idea:
       // Try labels every 1, 2, 5, 10, 20, 50, 100, etc.
       // Calculate the resulting tick spacing (i.e. this.height_ / nTicks).
       // The first spacing greater than pixelsPerYLabel is what we use.
       // TODO(danvk): version that works on a log scale.
       var kmg2 = opts("labelsKMG2");
+      var mults;
       if (kmg2) {
-        var mults = [1, 2, 4, 8];
+        mults = [1, 2, 4, 8];
       } else {
-        var mults = [1, 2, 5];
+        mults = [1, 2, 5];
       }
-      var scale, low_val, high_val, nTicks;
-      for (var i = -10; i < 50; i++) {
+      var scale, low_val, high_val;
+      for (i = -10; i < 50; i++) {
+        var base_scale;
         if (kmg2) {
-          var base_scale = Math.pow(16, i);
+          base_scale = Math.pow(16, i);
         } else {
-          var base_scale = Math.pow(10, i);
+          base_scale = Math.pow(10, i);
         }
-        for (var j = 0; j < mults.length; j++) {
+        var spacing = 0;
+        for (j = 0; j < mults.length; j++) {
           scale = base_scale * mults[j];
           low_val = Math.floor(a / scale) * scale;
           high_val = Math.ceil(b / scale) * scale;
           nTicks = Math.abs(high_val - low_val) / scale;
-          var spacing = pixels / nTicks;
+          spacing = pixels / nTicks;
           // wish I could break out of both loops at once...
           if (spacing > pixels_per_tick) break;
         }
@@ -142,8 +150,8 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
       // Construct the set of ticks.
       // Allow reverse y-axis if it's explicitly requested.
       if (low_val > high_val) scale *= -1;
-      for (var i = 0; i < nTicks; i++) {
-        var tickV = low_val + i * scale;
+      for (i = 0; i < nTicks; i++) {
+        tickV = low_val + i * scale;
         ticks.push( {v: tickV} );
       }
     }
@@ -157,7 +165,7 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
     k_labels = [ "K", "M", "B", "T" ];
   }
   if (opts("labelsKMG2")) {
-    if (k) self.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
+    if (k) Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
     k = 1024;
     k_labels = [ "k", "M", "G", "T" ];
   }
@@ -165,9 +173,9 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
   var formatter = opts('axisLabelFormatter');
 
   // Add labels to the ticks.
-  for (var i = 0; i < ticks.length; i++) {
+  for (i = 0; i < ticks.length; i++) {
     if (ticks[i].label !== undefined) continue;  // Use current label.
-    var tickV = ticks[i].v;
+    tickV = ticks[i].v;
     var absTickV = Math.abs(tickV);
     // TODO(danvk): set granularity to something appropriate here.
     var label = formatter(tickV, 0, opts, dygraph);
@@ -175,7 +183,7 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
       // TODO(danvk): should this be integrated into the axisLabelFormatter?
       // Round up to an appropriate unit.
       var n = k*k*k*k;
-      for (var j = 3; j >= 0; j--, n /= k) {
+      for (j = 3; j >= 0; j--, n /= k) {
         if (absTickV >= n) {
           label = Dygraph.round_(tickV / n, opts('digitsAfterDecimal')) +
               k_labels[j];
@@ -191,15 +199,7 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
 
 
 Dygraph.dateTicker = function(a, b, pixels, opts, dygraph, vals) {
-  var pixels_per_tick = opts('pixelsPerLabel');
-  var chosen = -1;
-  for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) {
-    var num_ticks = Dygraph.numDateTicks(a, b, i);
-    if (pixels / num_ticks >= pixels_per_tick) {
-      chosen = i;
-      break;
-    }
-  }
+  var chosen = Dygraph.pickDateTickGranularity(a, b, pixels, opts);
 
   if (chosen >= 0) {
     return Dygraph.getDateAxis(a, b, chosen, opts, dygraph);
@@ -269,6 +269,27 @@ Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
   return vals;
 }();
 
+/**
+ * Determine the correct granularity of ticks on a date axis.
+ *
+ * @param {Number} a Left edge of the chart (ms)
+ * @param {Number} b Right edge of the chart (ms)
+ * @param {Number} pixels Size of the chart in the relevant dimension (width).
+ * @param {Function} opts Function mapping from option name -> value.
+ * @return {Number} The appropriate axis granularity for this chart. See the
+ * enumeration of possible values in dygraph-tickers.js.
+ */
+Dygraph.pickDateTickGranularity = function(a, b, pixels, opts) {
+  var pixels_per_tick = opts('pixelsPerLabel');
+  for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) {
+    var num_ticks = Dygraph.numDateTicks(a, b, i);
+    if (pixels / num_ticks >= pixels_per_tick) {
+      return i;
+    }
+  }
+  return -1;
+};
+
 Dygraph.numDateTicks = function(start_time, end_time, granularity) {
   if (granularity < Dygraph.MONTHLY) {
     // Generate one tick mark for every fixed interval of time.
@@ -292,28 +313,30 @@ Dygraph.numDateTicks = function(start_time, end_time, granularity) {
 Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) {
   var formatter = opts("axisLabelFormatter");
   var ticks = [];
+  var t;
+
   if (granularity < Dygraph.MONTHLY) {
     // Generate one tick mark for every fixed interval of time.
     var spacing = Dygraph.SHORT_SPACINGS[granularity];
-    var format = '%d%b';  // e.g. "1Jan"
 
     // Find a time less than start_time which occurs on a "nice" time boundary
     // for this granularity.
     var g = spacing / 1000;
     var d = new Date(start_time);
+    var x;
     if (g <= 60) {  // seconds
-      var x = d.getSeconds(); d.setSeconds(x - x % g);
+      x = d.getSeconds(); d.setSeconds(x - x % g);
     } else {
       d.setSeconds(0);
       g /= 60;
       if (g <= 60) {  // minutes
-        var x = d.getMinutes(); d.setMinutes(x - x % g);
+        x = d.getMinutes(); d.setMinutes(x - x % g);
       } else {
         d.setMinutes(0);
         g /= 60;
 
         if (g <= 24) {  // days
-          var x = d.getHours(); d.setHours(x - x % g);
+          x = d.getHours(); d.setHours(x - x % g);
         } else {
           d.setHours(0);
           g /= 24;
@@ -326,7 +349,7 @@ Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) {
     }
     start_time = d.getTime();
 
-    for (var t = start_time; t <= end_time; t += spacing) {
+    for (t = start_time; t <= end_time; t += spacing) {
       ticks.push({ v:t,
                    label: formatter(new Date(t), granularity, opts, dg)
                  });
@@ -360,10 +383,10 @@ Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) {
     var end_year   = new Date(end_time).getFullYear();
     var zeropad = Dygraph.zeropad;
     for (var i = start_year; i <= end_year; i++) {
-      if (i % year_mod != 0) continue;
+      if (i % year_mod !== 0) continue;
       for (var j = 0; j < months.length; j++) {
         var date_str = i + "/" + zeropad(1 + months[j]) + "/01";
-        var t = Dygraph.dateStrToMillis(date_str);
+        t = Dygraph.dateStrToMillis(date_str);
         if (t < start_time || t > end_time) continue;
         ticks.push({ v:t,
                      label: formatter(new Date(t), granularity, opts, dg)
index db04314..8b9dfd9 100644 (file)
  * search) and generic DOM-manipulation functions.
  */
 
+/*jshint globalstrict: true */
+/*global Dygraph:false, G_vmlCanvasManager:false, Node:false, printStackTrace: false */
+"use strict";
+
 Dygraph.LOG_SCALE = 10;
 Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE);
 
 /** @private */
 Dygraph.log10 = function(x) {
   return Math.log(x) / Dygraph.LN_TEN;
-}
+};
 
 // Various logging levels.
 Dygraph.DEBUG = 1;
@@ -25,7 +29,12 @@ Dygraph.INFO = 2;
 Dygraph.WARNING = 3;
 Dygraph.ERROR = 3;
 
-// TODO(danvk): any way I can get the line numbers to be this.warn call?
+// Set this to log stack traces on warnings, etc.
+// This requires stacktrace.js, which is up to you to provide.
+// A copy can be found in the dygraphs repo, or at
+// https://github.com/eriwen/javascript-stacktrace
+Dygraph.LOG_STACK_TRACES = false;
+
 /**
  * @private
  * Log an error on the JS console at the given severity.
@@ -33,6 +42,24 @@ Dygraph.ERROR = 3;
  * @param { String } The message to log.
  */
 Dygraph.log = function(severity, message) {
+  var st;
+  if (typeof(printStackTrace) != 'undefined') {
+    // Remove uninteresting bits: logging functions and paths.
+    st = printStackTrace({guess:false});
+    while (st[0].indexOf("stacktrace") != -1) {
+      st.splice(0, 1);
+    }
+
+    st.splice(0, 2);
+    for (var i = 0; i < st.length; i++) {
+      st[i] = st[i].replace(/\([^)]*\/(.*)\)/, '@$1')
+          .replace(/\@.*\/([^\/]*)/, '@$1')
+          .replace('[object Object].', '');
+    }
+    var top_msg = st.splice(0, 1)[0];
+    message += ' (' + top_msg.replace(/^.*@ ?/, '') + ')';
+  }
+
   if (typeof(console) != 'undefined') {
     switch (severity) {
       case Dygraph.DEBUG:
@@ -49,6 +76,10 @@ Dygraph.log = function(severity, message) {
         break;
     }
   }
+
+  if (Dygraph.LOG_STACK_TRACES) {
+    console.log(st.join('\n'));
+  }
 };
 
 /** @private */
@@ -288,6 +319,7 @@ Dygraph.pageY = function(e) {
  * @return { Boolean } Whether the number is zero or NaN.
  */
 // TODO(danvk): rename this function to something like 'isNonZeroNan'.
+// TODO(danvk): determine when else this returns false (e.g. for undefined or null)
 Dygraph.isOK = function(x) {
   return x && !isNaN(x);
 };
@@ -330,7 +362,7 @@ Dygraph.floatFormat = function(x, opt_precision) {
   //
   // Finally, the argument for toExponential() is the number of trailing digits,
   // so we take off 1 for the value before the '.'.
-  return (Math.abs(x) < 1.0e-3 && x != 0.0) ?
+  return (Math.abs(x) < 1.0e-3 && x !== 0.0) ?
       x.toExponential(p - 1) : x.toPrecision(p);
 };
 
@@ -385,28 +417,31 @@ Dygraph.round_ = function(num, places) {
  * @param { Integer } [high] The last index in arry to consider (optional)
  */
 Dygraph.binarySearch = function(val, arry, abs, low, high) {
-  if (low == null || high == null) {
+  if (low === null || low === undefined ||
+      high === null || high === undefined) {
     low = 0;
     high = arry.length - 1;
   }
   if (low > high) {
     return -1;
   }
-  if (abs == null) {
+  if (abs === null || abs === undefined) {
     abs = 0;
   }
   var validIndex = function(idx) {
     return idx >= 0 && idx < arry.length;
-  }
-  var mid = parseInt((low + high) / 2);
+  };
+  var mid = parseInt((low + high) / 2, 10);
   var element = arry[mid];
   if (element == val) {
     return mid;
   }
+
+  var idx;
   if (element > val) {
     if (abs > 0) {
       // Accept if element > val, but also if prior element < val.
-      var idx = mid - 1;
+      idx = mid - 1;
       if (validIndex(idx) && arry[idx] < val) {
         return mid;
       }
@@ -416,7 +451,7 @@ Dygraph.binarySearch = function(val, arry, abs, low, high) {
   if (element < val) {
     if (abs < 0) {
       // Accept if element < val, but also if prior element > val.
-      var idx = mid + 1;
+      idx = mid + 1;
       if (validIndex(idx) && arry[idx] > val) {
         return mid;
       }
@@ -436,6 +471,11 @@ Dygraph.binarySearch = function(val, arry, abs, low, high) {
 Dygraph.dateParser = function(dateStr) {
   var dateStrSlashed;
   var d;
+
+  // Let the system try the format first.
+  d = Dygraph.dateStrToMillis(dateStr);
+  if (d && !isNaN(d)) return d;
+
   if (dateStr.search("-") != -1) {  // e.g. '2009-7-12' or '2009-07-12'
     dateStrSlashed = dateStr.replace("-", "/", "g");
     while (dateStrSlashed.search("-") != -1) {
@@ -444,8 +484,8 @@ Dygraph.dateParser = function(dateStr) {
     d = Dygraph.dateStrToMillis(dateStrSlashed);
   } else if (dateStr.length == 8) {  // e.g. '20090712'
     // TODO(danvk): remove support for this format. It's confusing.
-    dateStrSlashed = dateStr.substr(0,4) + "/" + dateStr.substr(4,2)
-                       + "/" + dateStr.substr(6,2);
+    dateStrSlashed = dateStr.substr(0,4) + "/" + dateStr.substr(4,2) + "/" +
+        dateStr.substr(6,2);
     d = Dygraph.dateStrToMillis(dateStrSlashed);
   } else {
     // Any format that Date.parse will accept, e.g. "2009/07/12" or
@@ -505,7 +545,7 @@ Dygraph.updateDeep = function (self, o) {
   if (typeof(o) != 'undefined' && o !== null) {
     for (var k in o) {
       if (o.hasOwnProperty(k)) {
-        if (o[k] == null) {
+        if (o[k] === null) {
           self[k] = null;
         } else if (Dygraph.isArrayLike(o[k])) {
           self[k] = o[k].slice();
@@ -579,7 +619,7 @@ Dygraph.clone = function(o) {
 Dygraph.createCanvas = function() {
   var canvas = document.createElement("canvas");
 
-  isIE = (/MSIE/.test(navigator.userAgent) && !window.opera);
+  var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera);
   if (isIE && (typeof(G_vmlCanvasManager) != 'undefined')) {
     canvas = G_vmlCanvasManager.initElement(canvas);
   }
@@ -589,6 +629,15 @@ Dygraph.createCanvas = function() {
 
 /**
  * @private
+ * Checks whether the user is on an Android browser.
+ * Android does not fully support the <canvas> tag, e.g. w/r/t/ clipping.
+ */
+Dygraph.isAndroid = function() {
+  return (/Android/).test(navigator.userAgent);
+};
+
+/**
+ * @private
  * Call a function N times at a given interval, then call a cleanup function
  * once. repeat_fn is called once immediately, then (times - 1) times
  * asynchronously. If times=1, then cleanup_fn() is also called synchronously.
@@ -613,7 +662,7 @@ Dygraph.repeatAndCleanup = function(repeat_fn, times, every_ms, cleanup_fn) {
     var target_time = start_time + (1 + count) * every_ms;
     setTimeout(function() {
       count++;
-      repeat_fn(count)
+      repeat_fn(count);
       if (count >= times - 1) {
         cleanup_fn();
       } else {
@@ -642,9 +691,6 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
     'axisLineColor': true,
     'axisLineWidth': true,
     'clickCallback': true,
-    'colorSaturation': true,
-    'colorValue': true,
-    'colors': true,
     'digitsAfterDecimal': true,
     'drawCallback': true,
     'drawPoints': true,
@@ -701,7 +747,7 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
   }
 
   // Iterate through the list of updated options.
-  for (property in attrs) {
+  for (var property in attrs) {
     // Break early if we already know we need new points from a previous option.
     if (requiresNewPoints) {
       break;
@@ -711,7 +757,7 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
       if (seriesNamesDictionary[property]) {
         // This property value is a list of options for this series.
         // If any of these sub properties are not pixel safe, set the flag.
-        for (subProperty in attrs[property]) {
+        for (var subProperty in attrs[property]) {
           // Break early if we already know we need new points from a previous option.
           if (requiresNewPoints) {
             break;
index 743219c..66432f7 100644 (file)
 
  */
 
+/*jshint globalstrict: true */
+/*global DygraphRangeSelector:false, DygraphLayout:false, DygraphCanvasRenderer:false, G_vmlCanvasManager:false */
+"use strict";
+
 /**
  * Creates an interactive, zoomable chart.
  *
@@ -57,7 +61,7 @@
  * whether the input data contains error ranges. For a complete list of
  * options, see http://dygraphs.com/options.html.
  */
-Dygraph = function(div, data, opts) {
+var Dygraph = function(div, data, opts) {
   if (arguments.length > 0) {
     if (arguments.length == 4) {
       // Old versions of dygraphs took in the series labels as a constructor
@@ -170,7 +174,7 @@ Dygraph.dateAxisFormatter = function(date, granularity) {
     return date.strftime('%b %y');
   } else {
     var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds();
-    if (frac == 0 || granularity >= Dygraph.DAILY) {
+    if (frac === 0 || granularity >= Dygraph.DAILY) {
       return new Date(date.getTime() + 3600*1000).strftime('%d%b');
     } else {
       return Dygraph.hmsString_(date.getTime());
@@ -289,7 +293,7 @@ Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) {
   // Labels is no longer a constructor parameter, since it's typically set
   // directly from the data source. It also conains a name for the x-axis,
   // which the previous constructor form did not.
-  if (labels != null) {
+  if (labels !== null) {
     var new_labels = ["Date"];
     for (var i = 0; i < labels.length; i++) new_labels.push(labels[i]);
     Dygraph.update(attrs, { 'labels': new_labels });
@@ -314,12 +318,12 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
       typeof(G_vmlCanvasManager) != 'undefined' &&
       document.readyState != 'complete') {
     var self = this;
-    setTimeout(function() { self.__init__(div, file, attrs) }, 100);
+    setTimeout(function() { self.__init__(div, file, attrs); }, 100);
     return;
   }
 
   // Support two-argument constructor
-  if (attrs == null) { attrs = {}; }
+  if (attrs === null || attrs === undefined) { attrs = {}; }
 
   attrs = Dygraph.mapLegacyOptions_(attrs);
 
@@ -339,7 +343,6 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   this.fractions_ = attrs.fractions || false;
   this.dateWindow_ = attrs.dateWindow || null;
 
-  this.wilsonInterval_ = attrs.wilsonInterval || true;
   this.is_initial_draw_ = true;
   this.annotations_ = [];
 
@@ -355,15 +358,15 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   // rules _except_ for an explicit 'width' or 'height' on the div.
   // As an added convenience, if the div has zero height (like <div></div> does
   // without any styles), then we use a default height/width.
-  if (div.style.width == '' && attrs.width) {
+  if (div.style.width === '' && attrs.width) {
     div.style.width = attrs.width + "px";
   }
-  if (div.style.height == '' && attrs.height) {
+  if (div.style.height === '' && attrs.height) {
     div.style.height = attrs.height + "px";
   }
-  if (div.style.height == '' && div.clientHeight == 0) {
+  if (div.style.height === '' && div.clientHeight === 0) {
     div.style.height = Dygraph.DEFAULT_HEIGHT + "px";
-    if (div.style.width == '') {
+    if (div.style.width === '') {
       div.style.width = Dygraph.DEFAULT_WIDTH + "px";
     }
   }
@@ -372,8 +375,8 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   this.height_ = div.clientHeight;
 
   // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
-  if (attrs['stackedGraph']) {
-    attrs['fillGraph'] = true;
+  if (attrs.stackedGraph) {
+    attrs.fillGraph = true;
     // TODO(nikhilk): Add any other stackedGraph checks here.
   }
 
@@ -412,9 +415,9 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
  */
 Dygraph.prototype.isZoomed = function(axis) {
   if (axis == null) return this.zoomed_x_ || this.zoomed_y_;
-  if (axis == 'x') return this.zoomed_x_;
-  if (axis == 'y') return this.zoomed_y_;
-  throw "axis parameter to Dygraph.isZoomed must be missing, 'x' or 'y'.";
+  if (axis === 'x') return this.zoomed_x_;
+  if (axis === 'y') return this.zoomed_y_;
+  throw "axis parameter is [" + axis + "] must be null, 'x' or 'y'.";
 };
 
 /**
@@ -422,9 +425,9 @@ Dygraph.prototype.isZoomed = function(axis) {
  */
 Dygraph.prototype.toString = function() {
   var maindiv = this.maindiv_;
-  var id = (maindiv && maindiv.id) ? maindiv.id : maindiv
+  var id = (maindiv && maindiv.id) ? maindiv.id : maindiv;
   return "[Dygraph " + id + "]";
-}
+};
 
 /**
  * @private
@@ -450,7 +453,7 @@ Dygraph.prototype.attr_ = function(name, seriesName) {
 // </REMOVE_FOR_COMBINED>
   if (seriesName &&
       typeof(this.user_attrs_[seriesName]) != 'undefined' &&
-      this.user_attrs_[seriesName] != null &&
+      this.user_attrs_[seriesName] !== null &&
       typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
     return this.user_attrs_[seriesName][name];
   } else if (typeof(this.user_attrs_[name]) != 'undefined') {
@@ -470,7 +473,7 @@ Dygraph.prototype.attr_ = function(name, seriesName) {
 Dygraph.prototype.optionsViewForAxis_ = function(axis) {
   var self = this;
   return function(opt) {
-    var axis_opts = self.user_attrs_['axes'];
+    var axis_opts = self.user_attrs_.axes;
     if (axis_opts && axis_opts[axis] && axis_opts[axis][opt]) {
       return axis_opts[axis][opt];
     }
@@ -480,7 +483,7 @@ Dygraph.prototype.optionsViewForAxis_ = function(axis) {
       return self.user_attrs_[opt];
     }
 
-    axis_opts = self.attrs_['axes'];
+    axis_opts = self.attrs_.axes;
     if (axis_opts && axis_opts[axis] && axis_opts[axis][opt]) {
       return axis_opts[axis][opt];
     }
@@ -572,14 +575,14 @@ Dygraph.prototype.toDomCoords = function(x, y, axis) {
  * Returns a single value or null if x is null.
  */
 Dygraph.prototype.toDomXCoord = function(x) {
-  if (x == null) {
+  if (x === null) {
     return null;
-  };
+  }
 
   var area = this.plotter_.area;
   var xRange = this.xAxisRange();
   return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;
-}
+};
 
 /**
  * Convert from data x coordinates to canvas/div Y coordinate and optional
@@ -590,12 +593,12 @@ Dygraph.prototype.toDomXCoord = function(x) {
 Dygraph.prototype.toDomYCoord = function(y, axis) {
   var pct = this.toPercentYCoord(y, axis);
 
-  if (pct == null) {
+  if (pct === null) {
     return null;
   }
   var area = this.plotter_.area;
   return area.y + pct * area.h;
-}
+};
 
 /**
  * Convert from canvas/div coords to data coordinates.
@@ -616,7 +619,7 @@ Dygraph.prototype.toDataCoords = function(x, y, axis) {
  * If x is null, this returns null.
  */
 Dygraph.prototype.toDataXCoord = function(x) {
-  if (x == null) {
+  if (x === null) {
     return null;
   }
 
@@ -632,7 +635,7 @@ Dygraph.prototype.toDataXCoord = function(x) {
  * if axis is null, this uses the first axis.
  */
 Dygraph.prototype.toDataYCoord = function(y, axis) {
-  if (y == null) {
+  if (y === null) {
     return null;
   }
 
@@ -644,7 +647,7 @@ Dygraph.prototype.toDataYCoord = function(y, axis) {
     return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);
   } else {
     // Computing the inverse of toDomCoord.
-    var pct = (y - area.y) / area.h
+    var pct = (y - area.y) / area.h;
 
     // Computing the inverse of toPercentYCoord. The function was arrived at with
     // the following steps:
@@ -687,12 +690,11 @@ Dygraph.prototype.toDataYCoord = function(y, axis) {
  * @return { Number } A fraction in [0, 1] where 0 = the top edge.
  */
 Dygraph.prototype.toPercentYCoord = function(y, axis) {
-  if (y == null) {
+  if (y === null) {
     return null;
   }
   if (typeof(axis) == "undefined") axis = 0;
 
-  var area = this.plotter_.area;
   var yRange = this.yAxisRange(axis);
 
   var pct;
@@ -706,7 +708,7 @@ Dygraph.prototype.toPercentYCoord = function(y, axis) {
     pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
   }
   return pct;
-}
+};
 
 /**
  * Converts an x value to a percentage from the left to the right of
@@ -722,7 +724,7 @@ Dygraph.prototype.toPercentYCoord = function(y, axis) {
  * @return { Number } A fraction in [0, 1] where 0 = the left edge.
  */
 Dygraph.prototype.toPercentXCoord = function(x) {
-  if (x == null) {
+  if (x === null) {
     return null;
   }
 
@@ -735,7 +737,7 @@ Dygraph.prototype.toPercentXCoord = function(x) {
  * @return { Integer } The number of columns.
  */
 Dygraph.prototype.numColumns = function() {
-  return this.rawData_[0].length;
+  return this.rawData_[0] ? this.rawData_[0].length : this.attr_("labels").length;
 };
 
 /**
@@ -747,6 +749,21 @@ Dygraph.prototype.numRows = function() {
 };
 
 /**
+ * Returns the full range of the x-axis, as determined by the most extreme
+ * values in the data set. Not affected by zooming, visibility, etc.
+ * TODO(danvk): merge w/ xAxisExtremes
+ * @return { Array<Number> } A [low, high] pair
+ * @private
+ */
+Dygraph.prototype.fullXRange_ = function() {
+  if (this.numRows() > 0) {
+    return [this.rawData_[0][0], this.rawData_[this.numRows() - 1][0]];
+  } else {
+    return [0, 1];
+  }
+};
+
+/**
  * Returns the value in the given row and column. If the row and column exceed
  * the bounds on the data, returns null. Also returns null if the value is
  * missing.
@@ -812,14 +829,6 @@ Dygraph.prototype.createInterface_ = function() {
     this.rangeSelector_.addToGraph(this.graphDiv, this.layout_);
   }
 
-  // Create the grapher
-  this.layout_ = new DygraphLayout(this);
-
-  if (this.rangeSelector_) {
-    // This needs to happen after the graph canvases are added to the div and the layout object is created.
-    this.rangeSelector_.addToGraph(this.graphDiv, this.layout_);
-  }
-
   var dygraph = this;
   Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) {
     dygraph.mouseMove_(e);
@@ -920,11 +929,12 @@ Dygraph.prototype.setColors_ = function() {
   var num = this.attr_("labels").length - 1;
   this.colors_ = [];
   var colors = this.attr_('colors');
+  var i;
   if (!colors) {
     var sat = this.attr_('colorSaturation') || 1.0;
     var val = this.attr_('colorValue') || 0.5;
     var half = Math.ceil(num / 2);
-    for (var i = 1; i <= num; i++) {
+    for (i = 1; i <= num; i++) {
       if (!this.visibility()[i-1]) continue;
       // alternate colors for high contrast.
       var idx = i % 2 ? Math.ceil(i / 2) : (half + i / 2);
@@ -932,7 +942,7 @@ Dygraph.prototype.setColors_ = function() {
       this.colors_.push(Dygraph.hsvToRGB(hue, sat, val));
     }
   } else {
-    for (var i = 0; i < num; i++) {
+    for (i = 0; i < num; i++) {
       if (!this.visibility()[i]) continue;
       var colorStr = colors[i % colors.length];
       this.colors_.push(colorStr);
@@ -958,10 +968,10 @@ Dygraph.prototype.getColors = function() {
  * @private
  */
 Dygraph.prototype.createStatusMessage_ = function() {
-  var userLabelsDiv = this.user_attrs_["labelsDiv"];
-  if (userLabelsDiv && null != userLabelsDiv
-    && (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String)) {
-    this.user_attrs_["labelsDiv"] = document.getElementById(userLabelsDiv);
+  var userLabelsDiv = this.user_attrs_.labelsDiv;
+  if (userLabelsDiv && null !== userLabelsDiv &&
+      (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String)) {
+    this.user_attrs_.labelsDiv = document.getElementById(userLabelsDiv);
   }
   if (!this.attr_("labelsDiv")) {
     var divWidth = this.attr_('labelsDivWidth');
@@ -1044,7 +1054,7 @@ Dygraph.prototype.createRollInterface_ = function() {
  * canvas (i.e. DOM Coords).
  */
 Dygraph.prototype.dragGetX_ = function(e, context) {
-  return Dygraph.pageX(e) - context.px
+  return Dygraph.pageX(e) - context.px;
 };
 
 /**
@@ -1053,7 +1063,7 @@ Dygraph.prototype.dragGetX_ = function(e, context) {
  * canvas (i.e. DOM Coords).
  */
 Dygraph.prototype.dragGetY_ = function(e, context) {
-  return Dygraph.pageY(e) - context.py
+  return Dygraph.pageY(e) - context.py;
 };
 
 /**
@@ -1298,7 +1308,6 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) {
   this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function() {
     if (that.attr_("zoomCallback")) {
       var xRange = that.xAxisRange();
-      var yRange = that.yAxisRange();
       that.attr_("zoomCallback")(xRange[0], xRange[1], that.yAxisRanges());
     }
   });
@@ -1312,13 +1321,13 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) {
  */
 Dygraph.prototype.doUnzoom_ = function() {
   var dirty = false, dirtyX = false, dirtyY = false;
-  if (this.dateWindow_ != null) {
+  if (this.dateWindow_ !== null) {
     dirty = true;
     dirtyX = true;
   }
 
   for (var i = 0; i < this.axes_.length; i++) {
-    if (this.axes_[i].valueWindow != null) {
+    if (this.axes_[i].valueWindow !== null) {
       dirty = true;
       dirtyY = true;
     }
@@ -1338,8 +1347,8 @@ Dygraph.prototype.doUnzoom_ = function() {
     // TODO(danvk): merge this block w/ the code below.
     if (!this.attr_("animatedZooms")) {
       this.dateWindow_ = null;
-      for (var i = 0; i < this.axes_.length; i++) {
-        if (this.axes_[i].valueWindow != null) {
+      for (i = 0; i < this.axes_.length; i++) {
+        if (this.axes_[i].valueWindow !== null) {
           delete this.axes_[i].valueWindow;
         }
       }
@@ -1369,7 +1378,7 @@ Dygraph.prototype.doUnzoom_ = function() {
       this.computeYAxisRanges_(extremes);
 
       newValueRanges = [];
-      for (var i = 0; i < this.axes_.length; i++) {
+      for (i = 0; i < this.axes_.length; i++) {
         newValueRanges.push(this.axes_[i].extremeRange);
       }
     }
@@ -1379,7 +1388,7 @@ Dygraph.prototype.doUnzoom_ = function() {
         function() {
           that.dateWindow_ = null;
           for (var i = 0; i < that.axes_.length; i++) {
-            if (that.axes_[i].valueWindow != null) {
+            if (that.axes_[i].valueWindow !== null) {
               delete that.axes_[i].valueWindow;
             }
           }
@@ -1400,18 +1409,19 @@ Dygraph.prototype.doAnimatedZoom = function(oldXRange, newXRange, oldYRanges, ne
 
   var windows = [];
   var valueRanges = [];
+  var step, frac;
 
-  if (oldXRange != null && newXRange != null) {
-    for (var step = 1; step <= steps; step++) {
-      var frac = Dygraph.zoomAnimationFunction(step, steps);
+  if (oldXRange !== null && newXRange !== null) {
+    for (step = 1; step <= steps; step++) {
+      frac = Dygraph.zoomAnimationFunction(step, steps);
       windows[step-1] = [oldXRange[0]*(1-frac) + frac*newXRange[0],
                          oldXRange[1]*(1-frac) + frac*newXRange[1]];
     }
   }
 
-  if (oldYRanges != null && newYRanges != null) {
-    for (var step = 1; step <= steps; step++) {
-      var frac = Dygraph.zoomAnimationFunction(step, steps);
+  if (oldYRanges !== null && newYRanges !== null) {
+    for (step = 1; step <= steps; step++) {
+      frac = Dygraph.zoomAnimationFunction(step, steps);
       var thisRange = [];
       for (var j = 0; j < this.axes_.length; j++) {
         thisRange.push([oldYRanges[j][0]*(1-frac) + frac*newYRanges[j][0],
@@ -1451,15 +1461,15 @@ Dygraph.prototype.mouseMove_ = function(event) {
   var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
 
   var lastx = -1;
-  var lasty = -1;
+  var i;
 
   // Loop through all the points and find the date nearest to our current
   // location.
   var minDist = 1e+100;
   var idx = -1;
-  for (var i = 0; i < points.length; i++) {
+  for (i = 0; i < points.length; i++) {
     var point = points[i];
-    if (point == null) continue;
+    if (point === null) continue;
     var dist = Math.abs(point.canvasx - canvasx);
     if (dist > minDist) continue;
     minDist = dist;
@@ -1471,7 +1481,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
   this.selPoints_ = [];
   var l = points.length;
   if (!this.attr_("stackedGraph")) {
-    for (var i = 0; i < l; i++) {
+    for (i = 0; i < l; i++) {
       if (points[i].xval == lastx) {
         this.selPoints_.push(points[i]);
       }
@@ -1479,7 +1489,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
   } else {
     // Need to 'unstack' points starting from the bottom
     var cumulative_sum = 0;
-    for (var i = l - 1; i >= 0; i--) {
+    for (i = l - 1; i >= 0; i--) {
       if (points[i].xval == lastx) {
         var p = {};  // Clone the point since we modify it
         for (var k in points[i]) {
@@ -1538,16 +1548,17 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
   // If no points are selected, we display a default legend. Traditionally,
   // this has been blank. But a better default would be a conventional legend,
   // which provides essential information for a non-interactive chart.
+  var html, sepLines, i, c;
   if (typeof(x) === 'undefined') {
     if (this.attr_('legend') != 'always') return '';
 
-    var sepLines = this.attr_('labelsSeparateLines');
+    sepLines = this.attr_('labelsSeparateLines');
     var labels = this.attr_('labels');
-    var html = '';
-    for (var i = 1; i < labels.length; i++) {
+    html = '';
+    for (i = 1; i < labels.length; i++) {
       if (!this.visibility()[i - 1]) continue;
-      var c = this.plotter_.colors[labels[i]];
-      if (html != '') html += (sepLines ? '<br/>' : ' ');
+      c = this.plotter_.colors[labels[i]];
+      if (html !== '') html += (sepLines ? '<br/>' : ' ');
       html += "<b><span style='color: " + c + ";'>&mdash;" + labels[i] +
         "</span></b>";
     }
@@ -1556,30 +1567,29 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
 
   var xOptView = this.optionsViewForAxis_('x');
   var xvf = xOptView('valueFormatter');
-  var html = xvf(x, xOptView, this.attr_('labels')[0], this) + ":";
+  html = xvf(x, xOptView, this.attr_('labels')[0], this) + ":";
 
   var yOptViews = [];
   var num_axes = this.numAxes();
-  for (var i = 0; i < num_axes; i++) {
+  for (i = 0; i < num_axes; i++) {
     yOptViews[i] = this.optionsViewForAxis_('y' + (i ? 1 + i : ''));
   }
   var showZeros = this.attr_("labelsShowZeroValues");
-  var sepLines = this.attr_("labelsSeparateLines");
-  for (var i = 0; i < this.selPoints_.length; i++) {
+  sepLines = this.attr_("labelsSeparateLines");
+  for (i = 0; i < this.selPoints_.length; i++) {
     var pt = this.selPoints_[i];
-    if (pt.yval == 0 && !showZeros) continue;
+    if (pt.yval === 0 && !showZeros) continue;
     if (!Dygraph.isOK(pt.canvasy)) continue;
     if (sepLines) html += "<br/>";
 
     var yOptView = yOptViews[this.seriesToAxisMap_[pt.name]];
     var fmtFunc = yOptView('valueFormatter');
-    var c = this.plotter_.colors[pt.name];
+    c = this.plotter_.colors[pt.name];
     var yval = fmtFunc(pt.yval, yOptView, pt.name, this);
 
     // TODO(danvk): use a template string here and make it an attribute.
-    html += " <b><span style='color: " + c + ";'>"
-      + pt.name + "</span></b>:"
-      + yval;
+    html += " <b><span style='color: " + c + ";'>" + pt.name +
+        "</span></b>:" + yval;
   }
   return html;
 };
@@ -1612,12 +1622,13 @@ Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
  */
 Dygraph.prototype.updateSelection_ = function() {
   // Clear the previously drawn vertical, if there is one
+  var i;
   var ctx = this.canvas_ctx_;
   if (this.previousVerticalX_ >= 0) {
     // Determine the maximum highlight circle size.
     var maxCircleSize = 0;
     var labels = this.attr_('labels');
-    for (var i = 1; i < labels.length; i++) {
+    for (i = 1; i < labels.length; i++) {
       var r = this.attr_('highlightCircleSize', labels[i]);
       if (r > maxCircleSize) maxCircleSize = r;
     }
@@ -1639,7 +1650,7 @@ Dygraph.prototype.updateSelection_ = function() {
     // Draw colored circles over the center of each selected point
     var canvasx = this.selPoints_[0].canvasx;
     ctx.save();
-    for (var i = 0; i < this.selPoints_.length; i++) {
+    for (i = 0; i < this.selPoints_.length; i++) {
       var pt = this.selPoints_[i];
       if (!Dygraph.isOK(pt.canvasy)) continue;
 
@@ -1720,7 +1731,7 @@ Dygraph.prototype.clearSelection = function() {
   this.setLegendHTML_();
   this.selPoints_ = [];
   this.lastx_ = -1;
-}
+};
 
 /**
  * Returns the number of the currently selected row. To get data for this row,
@@ -1760,7 +1771,7 @@ Dygraph.prototype.addXTicks_ = function() {
   if (this.dateWindow_) {
     range = [this.dateWindow_[0], this.dateWindow_[1]];
   } else {
-    range = [this.rawData_[0][0], this.rawData_[this.rawData_.length - 1][0]];
+    range = this.fullXRange_();
   }
 
   var xAxisOptionsView = this.optionsViewForAxis_('x');
@@ -1783,33 +1794,33 @@ Dygraph.prototype.addXTicks_ = function() {
  * @return [low, high]
  */
 Dygraph.prototype.extremeValues_ = function(series) {
-  var minY = null, maxY = null;
+  var minY = null, maxY = null, j, y;
 
   var bars = this.attr_("errorBars") || this.attr_("customBars");
   if (bars) {
     // With custom bars, maxY is the max of the high values.
-    for (var j = 0; j < series.length; j++) {
-      var y = series[j][1][0];
+    for (j = 0; j < series.length; j++) {
+      y = series[j][1][0];
       if (!y) continue;
       var low = y - series[j][1][1];
       var high = y + series[j][1][2];
       if (low > y) low = y;    // this can happen with custom bars,
       if (high < y) high = y;  // e.g. in tests/custom-bars.html
-      if (maxY == null || high > maxY) {
+      if (maxY === null || high > maxY) {
         maxY = high;
       }
-      if (minY == null || low < minY) {
+      if (minY === null || low < minY) {
         minY = low;
       }
     }
   } else {
-    for (var j = 0; j < series.length; j++) {
-      var y = series[j][1];
+    for (j = 0; j < series.length; j++) {
+      y = series[j][1];
       if (y === null || isNaN(y)) continue;
-      if (maxY == null || y > maxY) {
+      if (maxY === null || y > maxY) {
         maxY = y;
       }
-      if (minY == null || y < minY) {
+      if (minY === null || y < minY) {
         minY = y;
       }
     }
@@ -1855,7 +1866,7 @@ Dygraph.prototype.predraw_ = function() {
   // Convert the raw data (a 2D array) into the internal format and compute
   // rolling averages.
   this.rolledSeries_ = [null];  // x-axis is the first series and it's special
-  for (var i = 1; i < this.rawData_[0].length; i++) {
+  for (var i = 1; i < this.numColumns(); i++) {
     var connectSeparatedPoints = this.attr_('connectSeparatedPoints', i);
     var logScale = this.attr_('logscale', i);
     var series = this.extractSeries_(this.rawData_, i, logScale, connectSeparatedPoints);
@@ -1888,16 +1899,17 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
   var cumulative_y = [];  // For stacked series.
   var datasets = [];
   var extremes = {};  // series name -> [low, high]
+  var i, j, k;
 
   // Loop over the fields (series).  Go from the last to the first,
   // because if they're stacked that's how we accumulate the values.
   var num_series = rolledSeries.length - 1;
-  for (var i = num_series; i >= 1; i--) {
+  for (i = num_series; i >= 1; i--) {
     if (!this.visibility()[i - 1]) continue;
 
     // TODO(danvk): is this copy really necessary?
     var series = [];
-    for (var j = 0; j < rolledSeries[i].length; j++) {
+    for (j = 0; j < rolledSeries[i].length; j++) {
       series.push(rolledSeries[i][j]);
     }
 
@@ -1912,7 +1924,7 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
       // TODO(danvk): do binary search instead of linear search.
       // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
       var firstIdx = null, lastIdx = null;
-      for (var k = 0; k < series.length; k++) {
+      for (k = 0; k < series.length; k++) {
         if (series[k][0] >= low && firstIdx === null) {
           firstIdx = k;
         }
@@ -1925,7 +1937,7 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
       if (lastIdx === null) lastIdx = series.length - 1;
       if (lastIdx < series.length - 1) lastIdx++;
       boundaryIds[i-1] = [firstIdx, lastIdx];
-      for (var k = firstIdx; k <= lastIdx; k++) {
+      for (k = firstIdx; k <= lastIdx; k++) {
         pruned.push(series[k]);
       }
       series = pruned;
@@ -1936,14 +1948,16 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
     var seriesExtremes = this.extremeValues_(series);
 
     if (bars) {
-      for (var j=0; j<series.length; j++) {
-        val = [series[j][0], series[j][1][0], series[j][1][1], series[j][1][2]];
-        series[j] = val;
+      for (j=0; j<series.length; j++) {
+        series[j] = [series[j][0],
+                     series[j][1][0],
+                     series[j][1][1],
+                     series[j][1][2]];
       }
     } else if (this.attr_("stackedGraph")) {
       var l = series.length;
       var actual_y;
-      for (var j = 0; j < l; j++) {
+      for (j = 0; j < l; j++) {
         // If one data set has a NaN, let all subsequent stacked
         // sets inherit the NaN -- only start at 0 for the first set.
         var x = series[j][0];
@@ -1954,7 +1968,7 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
         actual_y = series[j][1];
         cumulative_y[x] += actual_y;
 
-        series[j] = [x, cumulative_y[x]]
+        series[j] = [x, cumulative_y[x]];
 
         if (cumulative_y[x] > seriesExtremes[1]) {
           seriesExtremes[1] = cumulative_y[x];
@@ -1996,10 +2010,9 @@ Dygraph.prototype.drawGraph_ = function(clearSelection) {
   var is_initial_draw = this.is_initial_draw_;
   this.is_initial_draw_ = false;
 
-  var minY = null, maxY = null;
   this.layout_.removeAllDatasets();
   this.setColors_();
-  this.attrs_['pointSize'] = 0.5 * this.attr_('highlightCircleSize');
+  this.attrs_.pointSize = 0.5 * this.attr_('highlightCircleSize');
 
   var packed = this.gatherDatasets_(this.rolledSeries_, this.dateWindow_);
   var datasets = packed[0];
@@ -2027,7 +2040,7 @@ Dygraph.prototype.drawGraph_ = function(clearSelection) {
   if (this.attr_("timingName")) {
     var end = new Date();
     if (console) {
-      console.log(this.attr_("timingName") + " - drawGraph: " + (end - start) + "ms")
+      console.log(this.attr_("timingName") + " - drawGraph: " + (end - start) + "ms");
     }
   }
 };
@@ -2038,10 +2051,10 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw, clearSelection) {
   this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
                                           this.canvas_.height);
 
-  if (is_initial_draw) {
-    // Generate a static legend before any particular point is selected.
-    this.setLegendHTML_();
-  } else {
+  // Generate a static legend before any particular point is selected.
+  this.setLegendHTML_();
+
+  if (!is_initial_draw) {
     if (clearSelection) {
       if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
         // We should select the point nearest the page x/y here, but it's easier
@@ -2077,10 +2090,10 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw, clearSelection) {
 Dygraph.prototype.computeYAxes_ = function() {
   // Preserve valueWindow settings if they exist, and if the user hasn't
   // specified a new valueRange.
-  var valueWindows;
-  if (this.axes_ != undefined && this.user_attrs_.hasOwnProperty("valueRange") == false) {
+  var i, valueWindows, seriesName, axis, index;
+  if (this.axes_ !== undefined && this.user_attrs_.hasOwnProperty("valueRange") === false) {
     valueWindows = [];
-    for (var index = 0; index < this.axes_.length; index++) {
+    for (index = 0; index < this.axes_.length; index++) {
       valueWindows.push(this.axes_[index].valueWindow);
     }
   }
@@ -2091,7 +2104,7 @@ Dygraph.prototype.computeYAxes_ = function() {
   // Get a list of series names.
   var labels = this.attr_("labels");
   var series = {};
-  for (var i = 1; i < labels.length; i++) series[labels[i]] = (i - 1);
+  for (i = 1; i < labels.length; i++) series[labels[i]] = (i - 1);
 
   // all options which could be applied per-axis:
   var axisOptions = [
@@ -2107,17 +2120,17 @@ Dygraph.prototype.computeYAxes_ = function() {
   ];
 
   // Copy global axis options over to the first axis.
-  for (var i = 0; i < axisOptions.length; i++) {
+  for (i = 0; i < axisOptions.length; i++) {
     var k = axisOptions[i];
     var v = this.attr_(k);
     if (v) this.axes_[0][k] = v;
   }
 
   // Go through once and add all the axes.
-  for (var seriesName in series) {
+  for (seriesName in series) {
     if (!series.hasOwnProperty(seriesName)) continue;
-    var axis = this.attr_("axis", seriesName);
-    if (axis == null) {
+    axis = this.attr_("axis", seriesName);
+    if (axis === null) {
       this.seriesToAxisMap_[seriesName] = 0;
       continue;
     }
@@ -2137,9 +2150,9 @@ Dygraph.prototype.computeYAxes_ = function() {
 
   // Go through one more time and assign series to an axis defined by another
   // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } }
-  for (var seriesName in series) {
+  for (seriesName in series) {
     if (!series.hasOwnProperty(seriesName)) continue;
-    var axis = this.attr_("axis", seriesName);
+    axis = this.attr_("axis", seriesName);
     if (typeof(axis) == 'string') {
       if (!this.seriesToAxisMap_.hasOwnProperty(axis)) {
         this.error("Series " + seriesName + " wants to share a y-axis with " +
@@ -2151,20 +2164,9 @@ Dygraph.prototype.computeYAxes_ = function() {
     }
   }
 
-  // Now we remove series from seriesToAxisMap_ which are not visible. We do
-  // this last so that hiding the first series doesn't destroy the axis
-  // properties of the primary axis.
-  var seriesToAxisFiltered = {};
-  var vis = this.visibility();
-  for (var i = 1; i < labels.length; i++) {
-    var s = labels[i];
-    if (vis[i - 1]) seriesToAxisFiltered[s] = this.seriesToAxisMap_[s];
-  }
-  this.seriesToAxisMap_ = seriesToAxisFiltered;
-
-  if (valueWindows != undefined) {
+  if (valueWindows !== undefined) {
     // Restore valueWindow settings.
-    for (var index = 0; index < valueWindows.length; index++) {
+    for (index = 0; index < valueWindows.length; index++) {
       this.axes_[index].valueWindow = valueWindows[index];
     }
   }
@@ -2204,8 +2206,8 @@ Dygraph.prototype.axisPropertiesForSeries = function(series) {
  */
 Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
   // Build a map from axis number -> [list of series names]
-  var seriesForAxis = [];
-  for (var series in this.seriesToAxisMap_) {
+  var seriesForAxis = [], series;
+  for (series in this.seriesToAxisMap_) {
     if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue;
     var idx = this.seriesToAxisMap_[series];
     while (seriesForAxis.length <= idx) seriesForAxis.push([]);
@@ -2221,40 +2223,43 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
       axis.extremeRange = [0, 1];
     } else {
       // Calculate the extremes of extremes.
-      var series = seriesForAxis[i];
+      series = seriesForAxis[i];
       var minY = Infinity;  // extremes[series[0]][0];
       var maxY = -Infinity;  // extremes[series[0]][1];
       var extremeMinY, extremeMaxY;
+
       for (var j = 0; j < series.length; j++) {
+        // this skips invisible series
+        if (!extremes.hasOwnProperty(series[j])) continue;
+
         // Only use valid extremes to stop null data series' from corrupting the scale.
         extremeMinY = extremes[series[j]][0];
-        if (extremeMinY != null) {
+        if (extremeMinY !== null) {
           minY = Math.min(extremeMinY, minY);
         }
         extremeMaxY = extremes[series[j]][1];
-        if (extremeMaxY != null) {
+        if (extremeMaxY !== null) {
           maxY = Math.max(extremeMaxY, maxY);
         }
       }
       if (axis.includeZero && minY > 0) minY = 0;
 
-      // Ensure we have a valid scale, otherwise defualt to zero for safety.
+      // Ensure we have a valid scale, otherwise default to [0, 1] for safety.
       if (minY == Infinity) minY = 0;
-      if (maxY == -Infinity) maxY = 0;
+      if (maxY == -Infinity) maxY = 1;
 
       // Add some padding and round up to an integer to be human-friendly.
       var span = maxY - minY;
       // special case: if we have no sense of scale, use +/-10% of the sole value.
-      if (span == 0) { span = maxY; }
+      if (span === 0) { span = maxY; }
 
-      var maxAxisY;
-      var minAxisY;
+      var maxAxisY, minAxisY;
       if (axis.logscale) {
-        var maxAxisY = maxY + 0.1 * span;
-        var minAxisY = minY;
+        maxAxisY = maxY + 0.1 * span;
+        minAxisY = minY;
       } else {
-        var maxAxisY = maxY + 0.1 * span;
-        var minAxisY = minY - 0.1 * span;
+        maxAxisY = maxY + 0.1 * span;
+        minAxisY = minY - 0.1 * span;
 
         // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense.
         if (!this.attr_("avoidMinZero")) {
@@ -2286,7 +2291,7 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
     // independent ticks, then that is permissible as well.
     var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));
     var ticker = opts('ticker');
-    if (i == 0 || axis.independentTicks) {
+    if (i === 0 || axis.independentTicks) {
       axis.ticks = ticker(axis.computedValueRange[0],
                           axis.computedValueRange[1],
                           this.height_,  // TODO(danvk): should be area.height
@@ -2337,7 +2342,7 @@ Dygraph.prototype.extractSeries_ = function(rawData, i, logScale, connectSeparat
       }
       series.push([x, point]);
     } else {
-      if (point != null || !connectSeparatedPoints) {
+      if (point !== null || !connectSeparatedPoints) {
         series.push([x, point]);
       }
     }
@@ -2361,15 +2366,16 @@ Dygraph.prototype.extractSeries_ = function(rawData, i, logScale, connectSeparat
 Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
   if (originalData.length < 2)
     return originalData;
-  var rollPeriod = Math.min(rollPeriod, originalData.length);
+  rollPeriod = Math.min(rollPeriod, originalData.length);
   var rollingData = [];
   var sigma = this.attr_("sigma");
 
+  var low, high, i, j, y, sum, num_ok, stddev;
   if (this.fractions_) {
     var num = 0;
     var den = 0;  // numerator/denominator
     var mult = 100.0;
-    for (var i = 0; i < originalData.length; i++) {
+    for (i = 0; i < originalData.length; i++) {
       num += originalData[i][1][0];
       den += originalData[i][1][1];
       if (i - rollPeriod >= 0) {
@@ -2380,22 +2386,22 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
       var date = originalData[i][0];
       var value = den ? num / den : 0.0;
       if (this.attr_("errorBars")) {
-        if (this.wilsonInterval_) {
+        if (this.attr_("wilsonInterval")) {
           // For more details on this confidence interval, see:
           // http://en.wikipedia.org/wiki/Binomial_confidence_interval
           if (den) {
             var p = value < 0 ? 0 : value, n = den;
             var pm = sigma * Math.sqrt(p*(1-p)/n + sigma*sigma/(4*n*n));
             var denom = 1 + sigma * sigma / den;
-            var low  = (p + sigma * sigma / (2 * den) - pm) / denom;
-            var high = (p + sigma * sigma / (2 * den) + pm) / denom;
+            low  = (p + sigma * sigma / (2 * den) - pm) / denom;
+            high = (p + sigma * sigma / (2 * den) + pm) / denom;
             rollingData[i] = [date,
                               [p * mult, (p - low) * mult, (high - p) * mult]];
           } else {
             rollingData[i] = [date, [0, 0, 0]];
           }
         } else {
-          var stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0;
+          stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0;
           rollingData[i] = [date, [mult * value, mult * stddev, mult * stddev]];
         }
       } else {
@@ -2403,16 +2409,16 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
       }
     }
   } else if (this.attr_("customBars")) {
-    var low = 0;
+    low = 0;
     var mid = 0;
-    var high = 0;
+    high = 0;
     var count = 0;
-    for (var i = 0; i < originalData.length; i++) {
+    for (i = 0; i < originalData.length; i++) {
       var data = originalData[i][1];
-      var y = data[1];
+      y = data[1];
       rollingData[i] = [originalData[i][0], [y, y - data[0], data[2] - y]];
 
-      if (y != null && !isNaN(y)) {
+      if (y !== null && !isNaN(y)) {
         low += data[0];
         mid += y;
         high += data[2];
@@ -2420,7 +2426,7 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
       }
       if (i - rollPeriod >= 0) {
         var prev = originalData[i - rollPeriod];
-        if (prev[1][1] != null && !isNaN(prev[1][1])) {
+        if (prev[1][1] !== null && !isNaN(prev[1][1])) {
           low -= prev[1][0];
           mid -= prev[1][1];
           high -= prev[1][2];
@@ -2438,18 +2444,17 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
   } else {
     // Calculate the rolling average for the first rollPeriod - 1 points where
     // there is not enough data to roll over the full number of points
-    var num_init_points = Math.min(rollPeriod - 1, originalData.length - 2);
     if (!this.attr_("errorBars")){
       if (rollPeriod == 1) {
         return originalData;
       }
 
-      for (var i = 0; i < originalData.length; i++) {
-        var sum = 0;
-        var num_ok = 0;
-        for (var j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
-          var y = originalData[j][1];
-          if (y == null || isNaN(y)) continue;
+      for (i = 0; i < originalData.length; i++) {
+        sum = 0;
+        num_ok = 0;
+        for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
+          y = originalData[j][1];
+          if (y === null || isNaN(y)) continue;
           num_ok++;
           sum += originalData[j][1];
         }
@@ -2461,19 +2466,19 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
       }
 
     } else {
-      for (var i = 0; i < originalData.length; i++) {
-        var sum = 0;
+      for (i = 0; i < originalData.length; i++) {
+        sum = 0;
         var variance = 0;
-        var num_ok = 0;
-        for (var j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
-          var y = originalData[j][1][0];
-          if (y == null || isNaN(y)) continue;
+        num_ok = 0;
+        for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
+          y = originalData[j][1][0];
+          if (y === null || isNaN(y)) continue;
           num_ok++;
           sum += originalData[j][1][0];
           variance += Math.pow(originalData[j][1][1], 2);
         }
         if (num_ok) {
-          var stddev = Math.sqrt(variance) / num_ok;
+          stddev = Math.sqrt(variance) / num_ok;
           rollingData[i] = [originalData[i][0],
                             [sum / num_ok, sigma * stddev, sigma * stddev]];
         } else {
@@ -2494,7 +2499,8 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
  */
 Dygraph.prototype.detectTypeFromString_ = function(str) {
   var isDate = false;
-  if (str.indexOf('-') > 0 ||
+  var dashPos = str.indexOf('-');  // could be 2006-01-01 _or_ 1.0e-2
+  if ((dashPos > 0 && (str[dashPos-1] != 'e' && str[dashPos-1] != 'E')) ||
       str.indexOf('/') >= 0 ||
       isNaN(parseFloat(str))) {
     isDate = true;
@@ -2573,6 +2579,7 @@ Dygraph.prototype.parseFloat_ = function(x, opt_line_no, opt_line) {
 Dygraph.prototype.parseCSV_ = function(data) {
   var ret = [];
   var lines = data.split("\n");
+  var vals, j;
 
   // Use the default delimiter or fall back to a tab if that makes sense.
   var delim = this.attr_('delimiter');
@@ -2595,7 +2602,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
   for (var i = start; i < lines.length; i++) {
     var line = lines[i];
     line_no = i;
-    if (line.length == 0) continue;  // skip blank lines
+    if (line.length === 0) continue;  // skip blank lines
     if (line[0] == '#') continue;    // skip comment lines
     var inFields = line.split(delim);
     if (inFields.length < 2) continue;
@@ -2610,9 +2617,9 @@ Dygraph.prototype.parseCSV_ = function(data) {
 
     // If fractions are expected, parse the numbers as "A/B"
     if (this.fractions_) {
-      for (var j = 1; j < inFields.length; j++) {
+      for (j = 1; j < inFields.length; j++) {
         // TODO(danvk): figure out an appropriate way to flag parse errors.
-        var vals = inFields[j].split("/");
+        vals = inFields[j].split("/");
         if (vals.length != 2) {
           this.error('Expected fractional "num/den" values in CSV data ' +
                      "but found a value '" + inFields[j] + "' on line " +
@@ -2630,32 +2637,32 @@ Dygraph.prototype.parseCSV_ = function(data) {
                    'but line ' + (1 + i) + ' has an odd number of values (' +
                    (inFields.length - 1) + "): '" + line + "'");
       }
-      for (var j = 1; j < inFields.length; j += 2) {
+      for (j = 1; j < inFields.length; j += 2) {
         fields[(j + 1) / 2] = [this.parseFloat_(inFields[j], i, line),
                                this.parseFloat_(inFields[j + 1], i, line)];
       }
     } else if (this.attr_("customBars")) {
       // Bars are a low;center;high tuple
-      for (var j = 1; j < inFields.length; j++) {
+      for (j = 1; j < inFields.length; j++) {
         var val = inFields[j];
         if (/^ *$/.test(val)) {
           fields[j] = [null, null, null];
         } else {
-          var vals = val.split(";");
+          vals = val.split(";");
           if (vals.length == 3) {
             fields[j] = [ this.parseFloat_(vals[0], i, line),
                           this.parseFloat_(vals[1], i, line),
                           this.parseFloat_(vals[2], i, line) ];
           } else {
-            this.warning('When using customBars, values must be either blank ' +
-                         'or "low;center;high" tuples (got "' + val +
-                         '" on line ' + (1+i));
+            this.warn('When using customBars, values must be either blank ' +
+                      'or "low;center;high" tuples (got "' + val +
+                      '" on line ' + (1+i));
           }
         }
       }
     } else {
       // Values are just numbers
-      for (var j = 1; j < inFields.length; j++) {
+      for (j = 1; j < inFields.length; j++) {
         fields[j] = this.parseFloat_(inFields[j], i, line);
       }
     }
@@ -2673,9 +2680,9 @@ Dygraph.prototype.parseCSV_ = function(data) {
     // first row parsed correctly, then they probably double-specified the
     // labels. We go with the values set in the option, discard this row and
     // log a warning to the JS console.
-    if (i == 0 && this.attr_('labels')) {
+    if (i === 0 && this.attr_('labels')) {
       var all_null = true;
-      for (var j = 0; all_null && j < fields.length; j++) {
+      for (j = 0; all_null && j < fields.length; j++) {
         if (fields[j]) all_null = false;
       }
       if (all_null) {
@@ -2690,7 +2697,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
 
   if (outOfOrder) {
     this.warn("CSV is out of order; order it correctly to speed loading.");
-    ret.sort(function(a,b) { return a[0] - b[0] });
+    ret.sort(function(a,b) { return a[0] - b[0]; });
   }
 
   return ret;
@@ -2706,20 +2713,21 @@ Dygraph.prototype.parseCSV_ = function(data) {
  */
 Dygraph.prototype.parseArray_ = function(data) {
   // Peek at the first x value to see if it's numeric.
-  if (data.length == 0) {
+  if (data.length === 0) {
     this.error("Can't plot empty data set");
     return null;
   }
-  if (data[0].length == 0) {
+  if (data[0].length === 0) {
     this.error("Data set cannot contain an empty row");
     return null;
   }
 
-  if (this.attr_("labels") == null) {
+  var i;
+  if (this.attr_("labels") === null) {
     this.warn("Using default labels. Set labels explicitly via 'labels' " +
               "in the options parameter");
     this.attrs_.labels = [ "X" ];
-    for (var i = 1; i < data[0].length; i++) {
+    for (i = 1; i < data[0].length; i++) {
       this.attrs_.labels.push("Y" + i);
     }
   }
@@ -2732,14 +2740,14 @@ Dygraph.prototype.parseArray_ = function(data) {
 
     // Assume they're all dates.
     var parsedData = Dygraph.clone(data);
-    for (var i = 0; i < data.length; i++) {
-      if (parsedData[i].length == 0) {
+    for (i = 0; i < data.length; i++) {
+      if (parsedData[i].length === 0) {
         this.error("Row " + (1 + i) + " of data is empty");
         return null;
       }
-      if (parsedData[i][0] == null
-          || typeof(parsedData[i][0].getTime) != 'function'
-          || isNaN(parsedData[i][0].getTime())) {
+      if (parsedData[i][0] === null ||
+          typeof(parsedData[i][0].getTime) != 'function' ||
+          isNaN(parsedData[i][0].getTime())) {
         this.error("x value in row " + (1 + i) + " is not a Date");
         return null;
       }
@@ -2790,7 +2798,8 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   var colIdx = [];
   var annotationCols = {};  // data index -> [annotation cols]
   var hasAnnotations = false;
-  for (var i = 1; i < cols; i++) {
+  var i, j;
+  for (i = 1; i < cols; i++) {
     var type = data.getColumnType(i);
     if (type == 'number') {
       colIdx.push(i);
@@ -2812,7 +2821,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   // Read column labels
   // TODO(danvk): add support back for errorBars
   var labels = [data.getColumnLabel(0)];
-  for (var i = 0; i < colIdx.length; i++) {
+  for (i = 0; i < colIdx.length; i++) {
     labels.push(data.getColumnLabel(colIdx[i]));
     if (this.attr_("errorBars")) i += 1;
   }
@@ -2822,7 +2831,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   var ret = [];
   var outOfOrder = false;
   var annotations = [];
-  for (var i = 0; i < rows; i++) {
+  for (i = 0; i < rows; i++) {
     var row = [];
     if (typeof(data.getValue(i, 0)) === 'undefined' ||
         data.getValue(i, 0) === null) {
@@ -2837,16 +2846,16 @@ Dygraph.prototype.parseDataTable_ = function(data) {
       row.push(data.getValue(i, 0));
     }
     if (!this.attr_("errorBars")) {
-      for (var j = 0; j < colIdx.length; j++) {
+      for (j = 0; j < colIdx.length; j++) {
         var col = colIdx[j];
         row.push(data.getValue(i, col));
         if (hasAnnotations &&
             annotationCols.hasOwnProperty(col) &&
-            data.getValue(i, annotationCols[col][0]) != null) {
+            data.getValue(i, annotationCols[col][0]) !== null) {
           var ann = {};
           ann.series = data.getColumnLabel(col);
           ann.xval = row[0];
-          ann.shortText = String.fromCharCode(65 /* A */ + annotations.length)
+          ann.shortText = String.fromCharCode(65 /* A */ + annotations.length);
           ann.text = '';
           for (var k = 0; k < annotationCols[col].length; k++) {
             if (k) ann.text += "\n";
@@ -2857,11 +2866,11 @@ Dygraph.prototype.parseDataTable_ = function(data) {
       }
 
       // Strip out infinities, which give dygraphs problems later on.
-      for (var j = 0; j < row.length; j++) {
+      for (j = 0; j < row.length; j++) {
         if (!isFinite(row[j])) row[j] = null;
       }
     } else {
-      for (var j = 0; j < cols - 1; j++) {
+      for (j = 0; j < cols - 1; j++) {
         row.push([ data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j) ]);
       }
     }
@@ -2873,14 +2882,14 @@ Dygraph.prototype.parseDataTable_ = function(data) {
 
   if (outOfOrder) {
     this.warn("DataTable is out of order; order it correctly to speed loading.");
-    ret.sort(function(a,b) { return a[0] - b[0] });
+    ret.sort(function(a,b) { return a[0] - b[0]; });
   }
   this.rawData_ = ret;
 
   if (annotations.length > 0) {
     this.setAnnotations(annotations, true);
   }
-}
+};
 
 /**
  * Get the CSV data. If it's in a function, call that function. If it's in a
@@ -2888,38 +2897,42 @@ Dygraph.prototype.parseDataTable_ = function(data) {
  * @private
  */
 Dygraph.prototype.start_ = function() {
-  if (typeof this.file_ == 'function') {
-    // CSV string. Pretend we got it via XHR.
-    this.loadedEvent_(this.file_());
-  } else if (Dygraph.isArrayLike(this.file_)) {
-    this.rawData_ = this.parseArray_(this.file_);
+  var data = this.file_;
+
+  // Functions can return references of all other types.
+  if (typeof data == 'function') {
+    data = data();
+  }
+
+  if (Dygraph.isArrayLike(data)) {
+    this.rawData_ = this.parseArray_(data);
     this.predraw_();
-  } else if (typeof this.file_ == 'object' &&
-             typeof this.file_.getColumnRange == 'function') {
+  } else if (typeof data == 'object' &&
+             typeof data.getColumnRange == 'function') {
     // must be a DataTable from gviz.
-    this.parseDataTable_(this.file_);
+    this.parseDataTable_(data);
     this.predraw_();
-  } else if (typeof this.file_ == 'string') {
+  } else if (typeof data == 'string') {
     // Heuristic: a newline means it's CSV data. Otherwise it's an URL.
-    if (this.file_.indexOf('\n') >= 0) {
-      this.loadedEvent_(this.file_);
+    if (data.indexOf('\n') >= 0) {
+      this.loadedEvent_(data);
     } else {
       var req = new XMLHttpRequest();
       var caller = this;
       req.onreadystatechange = function () {
         if (req.readyState == 4) {
-          if (req.status == 200 ||  // Normal http
-              req.status == 0) {    // Chrome w/ --allow-file-access-from-files
+          if (req.status === 200 ||  // Normal http
+              req.status === 0) {    // Chrome w/ --allow-file-access-from-files
             caller.loadedEvent_(req.responseText);
           }
         }
       };
 
-      req.open("GET", this.file_, true);
+      req.open("GET", data, true);
       req.send(null);
     }
   } else {
-    this.error("Unknown data format: " + (typeof this.file_));
+    this.error("Unknown data format: " + (typeof data));
   }
 };
 
@@ -2944,7 +2957,7 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) {
   if (typeof(block_redraw) == 'undefined') block_redraw = false;
 
   // mapLegacyOptions_ drops the "file" parameter as a convenience to us.
-  var file = input_attrs['file'];
+  var file = input_attrs.file;
   var attrs = Dygraph.mapLegacyOptions_(input_attrs);
 
   // TODO(danvk): this is a mess. Move these options into attr_.
@@ -2954,11 +2967,11 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) {
   if ('dateWindow' in attrs) {
     this.dateWindow_ = attrs.dateWindow;
     if (!('isZoomedIgnoreProgrammaticZoom' in attrs)) {
-      this.zoomed_x_ = attrs.dateWindow != null;
+      this.zoomed_x_ = (attrs.dateWindow !== null);
     }
   }
   if ('valueRange' in attrs && !('isZoomedIgnoreProgrammaticZoom' in attrs)) {
-    this.zoomed_y_ = attrs.valueRange != null;
+    this.zoomed_y_ = (attrs.valueRange !== null);
   }
 
   // TODO(danvk): validate per-series options.
@@ -3093,10 +3106,11 @@ Dygraph.prototype.visibility = function() {
   // Do lazy-initialization, so that this happens after we know the number of
   // data series.
   if (!this.attr_("visibility")) {
-    this.attrs_["visibility"] = [];
+    this.attrs_.visibility = [];
   }
-  while (this.attr_("visibility").length < this.rawData_[0].length - 1) {
-    this.attr_("visibility").push(true);
+  // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs.
+  while (this.attr_("visibility").length < this.numColumns() - 1) {
+    this.attrs_.visibility.push(true);
   }
   return this.attr_("visibility");
 };
@@ -3198,7 +3212,7 @@ Dygraph.addAnnotationRule = function() {
   }
 
   this.warn("Unable to add default annotation CSS rule; display may be off.");
-}
+};
 
 // Older pages may still use this name.
-DateGraph = Dygraph;
+var DateGraph = Dygraph;
diff --git a/experimental/palette/index.css b/experimental/palette/index.css
new file mode 100644 (file)
index 0000000..57ebaae
--- /dev/null
@@ -0,0 +1,9 @@
+@import url("palette.css");
+@import url("tooltip.css");
+@import url("textarea.css");
+
+#selector {
+  width: 150px;
+  margin-left: auto;
+  margin-right: auto;
+}
diff --git a/experimental/palette/index.html b/experimental/palette/index.html
new file mode 100644 (file)
index 0000000..ced58a9
--- /dev/null
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>Palette Demo</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../../excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../../dygraph-dev.js"></script>
+    <script type="text/javascript" src="options.js"></script>
+    <script type="text/javascript" src="textarea.js"></script>
+    <script type="text/javascript" src="tooltip.js"></script>
+    <script type="text/javascript" src="palette.js"></script>
+    <script type="text/javascript" src="samples.js"></script>
+    <script type="text/javascript" src="index.js"></script>
+    <link rel="stylesheet" type="text/css" href="index.css" />
+  </head>
+  <body>
+    <h2>Palette</h2>
+    <div style="font-size:smaller;"><p>To configure this chart, tweak the values on the right.
+    Enter applies changes, and the filter bar on the top reduces selections</p></div>
+    <table>
+      <tr>
+        <td valign="top">
+          <div id="selector">
+            <select></select>
+          </div>
+          <div id="graph"></div>
+          <div id="status" style="width:200px; font-size:0.8em; padding-top:5px;"></div>
+          Other messages:
+          <div id="messages"></div>
+          <div id="moreinstructions">
+            <h3>Learn By Doing</h3>
+            <ol>
+              <li>In the filter box, type <code>point</code>.</li>
+              <li>In the <em>drawPoints</em> text box, enter <code>true</code></li>
+              <li>Press <code>enter</code> or click <em>Refresh</em>. You will
+              see dots on the graph where points exist.</li>
+              <li>In the <em>pointSize</em> text box, enter <code>5</code></li>
+              <li>Press <code>enter</code> or click <em>Refresh</em>. The dots
+              just got larger.</li>
+              <li>Type <code>callback</code> in the filter box.</li>
+              <li>Instead of a text box, you will see buttons that can be used
+              to define callbacks. Click the button for <em>clickCallback</em>.</li>
+              <li>You will see that the prototype function body is
+              <code>function(e, x, points){ }</code>. Paste in this
+              replacement function:<p/>
+              <code>function(e, x){<br/>&nbsp;&nbsp;var elem =
+                document.getElementById("messages");<br/>&nbsp;&nbsp;elem.innerHTML =
+                elem.innerHTML + x + "&lt;br&gt;";<br/>}</code></li>
+              <li>Click the <em>OK</em> button. The <em>clickCallback</em>
+              button now says <code>defined</code> instead of <code>not defined</code>.</li>
+              <li>Click anywhere on the graph. The x-value will appear on the
+              page.</li>
+              <li>If you click directly on a point, then an alert box will
+              indicate that you've clicked on a point. That's because the
+              <em>pointClickCallback</em> was defined already.</li>
+              <li><b>Have fun and send feedback!</b></li>
+            </ol>
+            <h3>tips</h3>
+            The palette on the right contains (most) Dygraphs options. Here's
+            how they work:
+            <ul>
+              <li>The selection box on top can be used to choose your own data source.</li>
+              <li>Use the filter box to constrain your selection. For instance,
+              typing <code>draw</code> will show all options with that phrase in
+              its name.</li>
+              <li>Each field has specific types. The types are not documented,
+              but if you hover over an entry (or click on it) it will give you a
+              hint.</li>
+              <li>Enter a new value for the option, and hit the
+              <code>enter</code> key, or the <em>Refresh</em> button.</li>
+              <li>Some types require special consideration:
+                <li>integers, floats, strings and booleans can be entered
+                as-is.</li>
+                <li>arrays of elements can be comma-separated <em>except string
+                  arrays, which are (at the moment) semicolon-separated (anyone
+                  interested in making a good string array parser, take a shot.</em></li>
+                <li>Callbacks are defined in a (crappy tiny) dialog box. If the
+                function isn't defined, then a prototype of the function will be
+                presented to the user.</li>
+              </li>
+            </ul>
+
+            Then in the filter text box, type "point". Set drawPoints: true and
+            pointSize: 5.
+            You get the idea.
+          </div>
+        </td>
+        <td valign="top"><div id="optionsPalette"></div></td>
+     </tr>
+    </table>
+  </body>
+  <script type="text/javascript">Index.start();</script>
+</html>
diff --git a/experimental/palette/index.js b/experimental/palette/index.js
new file mode 100644 (file)
index 0000000..602bde4
--- /dev/null
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/** 
+ * @fileoverview Javascript to run index.html.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+
+"use strict";
+
+var Index = {};
+
+Index.splitVariables = function() { // http://www.idealog.us/2006/06/javascript_to_p.html
+  var query = window.location.search.substring(1); 
+  var args = {};
+  var vars = query.split("&"); 
+  for (var i = 0; i < vars.length; i++) { 
+    if (vars[i].length > 0) {
+      var pair = vars[i].split("="); 
+      args[pair[0]] = pair[1];
+    }
+  }
+  return args;
+}
+
+Index.draw = function(element, data, options) {
+  element.innerHTML = "";
+  element.removeAttribute("style");
+  var g = new Dygraph(
+    element,
+    data,
+    options
+  );
+  
+  // These don't work yet.
+  g.updateOptions({
+    labelsDiv: 'status',
+  });
+}
+
+Index.addMessage = function(text) {
+  var messages = document.getElementById("messages");
+  messages.textContent = messages.textContent + text + "\n";
+}
+
+Index.start = function() {
+  var variables = Index.splitVariables();
+  var sampleName = variables["sample"];
+  if (!(sampleName)) {
+    sampleName = "interestingShapes";
+  }
+  var sampleIndex = Samples.indexOf(sampleName);
+  var sample = Samples.data[sampleIndex];
+  var data = sample.data;
+  var redraw = function() {
+    Index.draw(document.getElementById("graph"), data, palette.read());
+  }
+
+  var selector = document.getElementById("selector").getElementsByTagName("select")[0];
+  for (var idx in Samples.data) {
+    var entry = Samples.data[idx];
+    var option = document.createElement("option");
+    option.value = entry.id;
+    option.textContent = entry.title;
+    selector.appendChild(option);
+  }
+  selector.onchange = function() {
+    var id = selector.options[selector.selectedIndex].value;
+    var url = document.URL;
+    var qmIndex = url.indexOf("?");
+    if (qmIndex >= 0) {
+      url = url.substring(0, qmIndex);
+    }
+    url = url + "?sample=" + id;
+    for (var idx in variables) {
+      if (idx != "sample") {
+        url = url + "&" + idx + "=" + variables[idx];
+      }
+    }
+    window.location = url;
+  }
+  selector.selectedIndex = sampleIndex;
+  var palette = new Palette();
+  palette.create(document, document.getElementById("optionsPalette"));
+  palette.write(sample.options);
+  palette.onchange = redraw;
+  palette.filterBar.focus();
+  redraw();
+
+  for (var opt in Dygraph.OPTIONS_REFERENCE) {
+    if (!(opt in opts)) {
+      var entry = Dygraph.OPTIONS_REFERENCE[opt];
+      console.warn("missing option: " + opt + " of type " + entry.type);
+    }
+  }
+}
diff --git a/experimental/palette/options.js b/experimental/palette/options.js
new file mode 100644 (file)
index 0000000..e590693
--- /dev/null
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/** 
+ * @fileoverview List of options and their types, used for the palette.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+
+"use strict";
+
+var opts = {
+  animatedZooms: { type: "boolean" },
+  annotationClickHandler : {type: "function(annotation, point, dygraph, event)"},
+  annotationDblClickHandler : {type: "function(annotation, point, dygraph, event)"},
+  annotationMouseOutHandler : {type: "function(annotation, point, dygraph, event)"},
+  annotationMouseOverHandler : {type: "function(annotation, point, dygraph, event)"},
+  avoidMinZero: { type: "boolean" },
+  axisLabelColor: { type: "string" },
+  axisLabelFontSize: {type: "int" },
+  axisLabelWidth: {type: "int" },
+  axisLineColor: {type: "string" },
+  axisLineWidth: {type: "int" },
+  axisTickSize: {type: "int" },
+  clickCallback : {type: "function(e, x, points)"},
+  colorSaturation: { type: "float" },
+  colors: { type: "array<string>" },
+  colorValue: { type: "float" },
+  connectSeparatedPoints: { type: "boolean" },
+  customBars: { type: "boolean" },
+  dateWindow: { type: "array<Date>"},
+  delimiter: { type: "string" },
+  digitsAfterDecimal: { type: "int"},
+  displayAnnotations: { type: "boolean" },
+  drawCallback : {type: "function(dygraph, is_initial)"},
+  drawPoints: { type: "boolean" },
+  drawXAxis: {type: "boolean" },
+  drawXGrid: {type: "boolean" },
+  drawYAxis: {type: "boolean" },
+  drawYGrid: {type: "boolean" },
+  errorBars: { type: "boolean" },
+  fillAlpha: { type: "float" },
+  fillGraph: { type: "boolean" },
+  fractions: { type: "boolean" },
+  gridLineColor: { type: "string" },
+  gridLineWidth: { type: "int" },
+  height: {type: "int"},
+  hideOverlayOnMouseOut: { type: "boolean" },
+  highlightCallback : {type: "function(event, x, points,row)"},
+  highlightCircleSize: { type: "int" },
+  includeZero: { type: "boolean" },
+  isZoomedIgnoreProgrammaticZoom: {type: "boolean" },
+  labelsDivWidth: {type: "integer"},
+  labels: {type: "array<string>" },
+  labelsKMB: {type: "boolean" },
+  labelsKMG2: {type: "boolean"},
+  labelsSeparateLines: {type: "boolean"},
+  labelsShowZeroValues: {type: "boolean"},
+  legend: {type: "string"},
+  logscale: { type: "boolean" },
+  maxNumberWidth: {type: "int"},
+  panEdgeFraction: { type: "float" },
+  pixelsPerLabel: { type: "int" },
+  pixelsPerXLabel: { type: "int" },
+  pixelsPerYLabel: { type: "int" },
+  pointClickCallback : {type: "function(e, point)"},
+  pointSize: { type: "integer" },
+  rangeSelectorHeight: { type: "int" },
+  rangeSelectorPlotFillColor: { type: "int" },
+  rangeSelectorPlotStrokeColor: { type: "int" },
+  rightGap: {type: "boolean"},
+  rollPeriod: {type: "int"},
+  showLabelsOnHighlight: { type: "boolean" },
+  showRangeSelector: { type: "boolean" },
+  showRoller: {type: "boolean" },
+  sigFigs: {type: "int"},
+  sigma: { type: "float" },
+  stackedGraph: { type: "boolean" },
+  stepPlot: { type: "boolean" },
+  strokeWidth: { type: "integer" },
+  timingName: { type: "string" },
+  title: {type: "string"},
+  titleHeight: {type: "integer"},
+  underlayCallback : {type: "function(canvas, area, dygraph)"},
+  unhighlightCallback : {type: "function(event)"},
+  valueRange: {type: "array<float>"},
+  visibility: {type: "array<boolean>"},
+  width: {type: "int"},
+  wilsonInterval: { type: "boolean" },
+  xAxisHeight: {type: "int"},
+  xAxisLabelWidth: {type: "int"},
+  xLabelHeight: {type: "int"},
+  xlabel : {type: "string" },
+  xValueParser : {type: "function(str)"},
+  yAxisLabelWidth: {type: "int"},
+  yLabelWidth: {type: "int"},
+  ylabel : {type: "string" },
+  zoomCallback : {type: "function(minDate, maxDate, yRanges)"},
+};
diff --git a/experimental/palette/palette.css b/experimental/palette/palette.css
new file mode 100644 (file)
index 0000000..28ab8a8
--- /dev/null
@@ -0,0 +1,38 @@
+.palette {
+  border-style: solid;
+  border-width: 1px;
+  border-color: #bbbbbb;
+  font-size: smaller;
+  font-family: Arial, Verdana, sans-serif;
+  width: 300px;
+  padding:2px;
+  color: #333333;
+}
+
+.palette .header {
+  background-color: #b0c0e0;
+}
+
+.palette .odd {
+  background-color: #ccddff;
+}
+
+.palette .even {
+  background-color: #c6d6f4;
+}
+
+.palette .name {
+}
+
+.palette .name:after {
+  content: ":";
+}
+
+.palette .option {
+}
+
+.palette .textInput {
+  padding: 1px;
+  border: 2px inset;
+  border-color: #EEE;
+}
diff --git a/experimental/palette/palette.js b/experimental/palette/palette.js
new file mode 100644 (file)
index 0000000..d7c7cab
--- /dev/null
@@ -0,0 +1,248 @@
+// Copyright (c) 2011 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/** 
+ * @fileoverview Dygraphs options palette.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+"use strict";
+
+function Palette() {
+  this.model = {};
+  this.onchange = function() {};
+  this.filterBar = null;
+}
+
+Palette.createChild = function(type, parentElement, className) {
+  var element = document.createElement(type);
+  parentElement.appendChild(element);
+  if (className) {
+    element.className = className;
+  }
+  return element;
+};
+
+Palette.prototype.create = function(document, parentElement) {
+  var palette = this;
+
+  var table = Palette.createChild("div", parentElement, "palette");
+  table.width="300px";
+
+  this.tooltip = new Tooltip();
+
+  var row = Palette.createChild("div", table, "header");
+  row.style.visibility = "visible";
+
+  Palette.createChild("span", row).textContent = "Filter:";
+  this.filterBar = Palette.createChild("input", Palette.createChild("span", row));
+  this.filterBar.type = "search";
+  this.filterBar.onkeyup = function() {
+    palette.filter(palette.filterBar.value)
+  };
+  this.filterBar.onclick = this.filterBar.onkeyup;
+  var go = Palette.createChild("button", Palette.createChild("span", row));
+  go.textContent = "Redraw"
+  go.onclick = function() {
+    palette.onchange();
+  };
+
+  // CURRENTLY HIDDEN.
+  var tmp = Palette.createChild("button", Palette.createChild("span", row));
+  tmp.textContent = "Copy"
+  tmp.onclick = function() {
+    var textarea = new TextArea();
+    textarea.show("header", "Now is the time for all good men\nto come to the aid of their country");
+  };
+  tmp.style.display = "none";
+
+  for (var opt in opts) {
+    try {
+      if (opts.hasOwnProperty(opt)) {
+        var type = opts[opt].type;
+        var isFunction = type.indexOf("function(") == 0;
+        var row = Palette.createChild("div", table);
+        row.onmouseover = function(source, title, type, body, e) {
+          return function(e) {
+            palette.tooltip.show(source, e, title, type, body);
+          };
+        } (row, opt, type, Dygraph.OPTIONS_REFERENCE[opt].description);
+        row.onmouseout = function() { palette.tooltip.hide(); };
+
+        var div = Palette.createChild("span", row, "name");
+        div.textContent = opt;
+
+        var value = Palette.createChild("span", row, "option");
+
+        if (isFunction) {
+           var input = Palette.createChild("button", value);
+           input.onclick = function(opt, palette) {
+             return function(event) {
+               var entry = palette.model[opt];
+               var inputValue = entry.functionString;
+               if (inputValue == null || inputValue.length == 0) {
+                 inputValue = opts[opt].type + "{\n\n}";
+               }
+              var textarea = new TextArea();
+              textarea.show(opt, inputValue);
+              textarea.okCallback = function(value) {
+                 if (value != inputValue) {
+                   entry.functionString = value;
+                   entry.input.textContent = value ? "defined" : "not defined";
+                   palette.onchange();
+                 }
+               }
+             }
+           }(opt, this);
+        } else {
+          var input = Palette.createChild("input", value, "textInput");
+          if (type == "boolean") {
+            input.size = "5";
+            input.maxlength = "5";
+          }
+          input.onkeypress = function(event) {
+            var keycode = event.which;
+            if (keycode == 13 || keycode == 8) {
+              palette.onchange();
+            }
+          }
+
+          input.type="text";
+        }
+        this.model[opt] = { input: input, row: row };
+      }
+    } catch(err) {
+      throw "For option " + opt + ":" + err;
+    }
+  }
+  this.filter("");
+}
+
+// TODO: replace semicolon parsing with comma parsing, and supporting quotes.
+Palette.parseStringArray = function(value) {
+  if (value == null || value.length == 0) {
+    return null;
+  }
+  return value.split(";");
+}
+
+Palette.parseBooleanArray = function(value) {
+  if (value == null || value.length == 0) {
+    return null;
+  }
+  return value.split(',').map(function(x) { return x.trim() == "true"; });
+}
+
+Palette.parseFloatArray = function(value) {
+  if (value == null || value.length == 0) {
+    return null;
+  }
+  return value.split(',').map(function(x) { return parseFloat(x); });
+}
+
+Palette.parseIntArray = function(value) {
+  if (value == null || value.length == 0) {
+    return null;
+  }
+  return value.split(',').map(function(x) { return parseInt(x); });
+}
+
+Palette.prototype.read = function() {
+  var results = {};
+  for (var opt in this.model) {
+    if (this.model.hasOwnProperty(opt)) {
+      var type = opts[opt].type;
+      var isFunction = type.indexOf("function(") == 0;
+      var input = this.model[opt].input;
+      var value = isFunction ? this.model[opt].functionString : input.value;
+      if (value && value.length != 0) {
+        if (type == "boolean") {
+          results[opt] = value == "true";
+        } else if (type == "int") {
+          results[opt] = parseInt(value);
+        } else if (type == "float") {
+          results[opt] = parseFloat(value);
+        } else if (type == "array<string>") {
+          results[opt] = Palette.parseStringArray(value);
+        } else if (type == "array<float>") {
+          results[opt] = Palette.parseFloatArray(value);
+        } else if (type == "array<boolean>") {
+          results[opt] = Palette.parseBooleanArray(value);
+        } else if (type == "array<Date>") {
+          results[opt] = Palette.parseIntArray(value);
+        } else if (isFunction) {
+          var localVariable = null;
+          eval("localVariable = " + value);
+          results[opt] = localVariable;
+        } else {
+          results[opt] = value;
+        }
+      }
+    }
+  }
+  return results;
+}
+
+/**
+ * Write to input elements.
+ */
+Palette.prototype.write = function(hash) {
+  var results = {};
+  for (var opt in this.model) {
+    //  && hash.hasOwnProperty(opt)
+    if (this.model.hasOwnProperty(opt)) {
+      var input = this.model[opt].input;
+      var type = opts[opt].type;
+      var value = hash[opt];
+      if (type == "array<string>") {
+        if (value) {
+          input.value = value.join("; ");
+        }
+      } else if (type.indexOf("array") == 0) {
+        if (value) {
+          input.value = value.join(", ");
+        }
+      } else if (type.indexOf("function(") == 0) {
+        input.textContent = value ? "defined" : "not defined";
+        this.model[opt].functionString = value ? value.toString() : null;
+      } else {
+        if (value) {
+          input.value = value;
+        }
+      }
+    }
+  }
+}
+
+Palette.prototype.filter = function(pattern) {
+  pattern = pattern.toLowerCase();
+  var even = true;
+  for (var opt in this.model) {
+    if (this.model.hasOwnProperty(opt)) {
+      var row = this.model[opt].row;
+      var matches = opt.toLowerCase().indexOf(pattern) >= 0;
+      row.style.display = matches ? "block" : "none";
+      if (matches) {
+        row.className = even ? "even" : "odd";
+        even = !even;
+      }
+    }
+  }
+}
diff --git a/experimental/palette/samples.js b/experimental/palette/samples.js
new file mode 100644 (file)
index 0000000..ef4fbbf
--- /dev/null
@@ -0,0 +1,148 @@
+// Copyright (c) 2012 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/** 
+ * @fileoverview Source samples.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+
+"use strict";
+
+var Samples = {};
+Samples.data = [
+  {
+    id: "interestingShapes",
+    title: "Interesting Shapes",
+    data: function() {
+      var zp = function(x) { if (x < 10) return "0"+x; else return x; };
+      var r = "date,parabola,line,another line,sine wave\n";
+      for (var i=1; i<=31; i++) {
+        r += "201110" + zp(i);
+        r += "," + 10*(i*(31-i));
+        r += "," + 10*(8*i);
+        r += "," + 10*(250 - 8*i);
+        r += "," + 10*(125 + 125 * Math.sin(0.3*i));
+        r += "\n";
+      }
+      return r;
+    },
+    options: {
+      colors: [
+        "rgb(51,204,204)",
+        "rgb(255,100,100)",
+        "#00DD55",
+        "rgba(50,50,200,0.4)"
+      ],
+      labelsSeparateLines: true,
+      labelsKMB: true,
+      legend: 'always',
+      width: 640,
+      height: 480,
+      title: 'Interesting Shapes',
+      xlabel: 'Date',
+      ylabel: 'Count',
+      axisLineColor: 'white',
+      drawXGrid: false,
+      pointClickCallback: function() {
+  alert("p-click!");
+},
+    }
+  },
+  
+  {
+    id: "sparse",
+    title: "Sparse Data",
+    data: [
+      [ new Date("2009/12/01"), 10, 10, 10],
+      [ new Date("2009/12/02"), 15, 11, 12],
+      [ new Date("2009/12/03"), null, null, 12],
+      [ new Date("2009/12/04"), 20, 14, null],
+      [ new Date("2009/12/05"), 15, null, 17],
+      [ new Date("2009/12/06"), 18, null, null],
+      [ new Date("2009/12/07"), 12, 14, null]
+    ],
+    options: {
+      labels: ["Date", "Series1", "Series2", "Series3"]
+    }
+  },
+  
+  {
+    id: "manyPoints",
+    title: "Dense Data",
+    data: function() {
+      var numPoints = 1000;
+      var numSeries = 100;
+  
+      var data = [];
+      var xmin = 0.0;
+      var xmax = 2.0 * Math.PI;
+      var adj = .5;
+      var delta = (xmax - xmin) / (numPoints - 1);
+  
+      for (var i = 0; i < numPoints; ++i) {
+        var x = xmin + delta * i;
+        var elem = [ x ];
+        for (var j = 0; j < numSeries; j++) {
+          var y = Math.pow(Math.random() - Math.random(), 7);
+          elem.push(y);
+        }
+        data[i] = elem;
+      }
+      return data;
+    },
+    options: {
+      labelsSeparateLines: true,
+      width: 640,
+      height: 480,
+      title: 'Many Points',
+      axisLineColor: 'white',
+    }
+  },
+
+  {
+    id: "errorBars",
+    title: "Error Bars",
+    data: [
+      [1, [10,  10, 100]],
+      [2, [15,  20, 110]],
+      [3, [10,  30, 100]],
+      [4, [15,  40, 110]],
+      [5, [10, 120, 100]],
+      [6, [15,  50, 110]],
+      [7, [10,  70, 100]],
+      [8, [15,  90, 110]],
+      [9, [10,  50, 100]]
+    ],
+    options: {
+      customBars: true,
+      errorBars: true
+    }
+  }
+];
+
+Samples.indexOf = function(id) {
+  for (var idx in Samples.data) {
+    if (Samples.data[idx].id == id) {
+      return idx;
+    }
+  }
+  return null;
+}
diff --git a/experimental/palette/textarea.css b/experimental/palette/textarea.css
new file mode 100644 (file)
index 0000000..85e0bb8
--- /dev/null
@@ -0,0 +1,38 @@
+.textarea {
+  position: absolute;
+  border: 1px solid black;
+  background-color: #dddddd;
+  z-index: 12;
+}
+
+.textarea .prompt {
+  padding-left: 2px;
+}
+
+.textarea .buttons {
+  position: absolute;
+  bottom: 5px;
+  right: 5px;
+}
+
+.textarea button {
+  color: #222222;
+}
+
+.textarea .editor {
+  font-family: Inconsolata, Courier New, Courier;
+  margin: 4px;
+}
+
+#modalBackground {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 11;
+  background-color:#333333;
+  display: none;
+  opacity: 0.40;
+  filter: alpha(opacity=40)
+}
diff --git a/experimental/palette/textarea.js b/experimental/palette/textarea.js
new file mode 100644 (file)
index 0000000..3f97a89
--- /dev/null
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/** 
+ * @fileoverview Dygraphs options palette text area.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+"use strict";
+
+function TextArea(parent) {
+  var body = document.getElementsByTagName("body")[0];
+  if (!parent) {
+    parent = body;
+  }
+  this.elem = Palette.createChild("div", parent, "textarea");
+  this.title = Palette.createChild("div", this.elem, "title");
+  this.textarea = Palette.createChild("textarea", this.elem, "editor");
+  this.buttons = Palette.createChild("div", this.elem, "buttons");
+  this.ok = Palette.createChild("button", this.buttons);
+  this.ok.textContent = "OK";
+  this.cancel = Palette.createChild("button", this.buttons);
+  this.cancel.textContent = "Cancel";
+
+  var textarea = this;
+  this.ok.onclick = function() {
+    textarea.hide();
+    textarea.okCallback(textarea.textarea.value);
+  };
+  this.cancel.onclick = function() {
+    textarea.hide();
+    textarea.cancelCallback();
+  };
+  this.reposition = function() {
+    var left = (document.documentElement.clientWidth - textarea.elem.offsetWidth) / 2;
+    var top = (document.documentElement.clientHeight - textarea.elem.offsetHeight) / 2;
+    console.log("reposition", left, top);
+    textarea.elem.style.left = Math.max(left, 0) + "px";
+    textarea.elem.style.top = Math.max(top, 0) + "px";
+  }
+
+  this.background = Palette.createChild("div", body, "background");
+  this.background.id = "modalBackground";
+  this.hide();
+}
+
+TextArea.prototype.cancelCallback = function() {
+};
+
+TextArea.prototype.okCallback = function(content) {
+};
+
+TextArea.prototype.show = function(title, content) {
+  this.title.textContent = title;
+  this.textarea.value = content;
+
+  var height = 315;
+  var width = 445;
+
+
+  var sums = function(adds, subtracts, field) {
+    var total = 0;
+    for (var idx in adds) {
+      total += parseInt(adds[idx][field]);
+    }
+    for (var idx2 in subtracts) {
+      total -= parseInt(subtracts[idx2][field]);
+    }
+    return total;
+  }
+  this.elem.style.display = "block";
+  this.background.style.display = "block";
+
+  this.elem.style.height = height + "px";
+  this.elem.style.width = width + "px";
+
+  this.textarea.style.height = (-18 + sums([this.elem], [this.title, this.buttons], "offsetHeight")) + "px";
+  this.textarea.style.width = (-16 + sums([this.elem], [ ], "offsetWidth")) + "px";
+
+  this.reposition();
+  window.addEventListener('resize', this.reposition, false);
+  document.documentElement.addEventListener('onscroll', this.reposition);
+}
+
+TextArea.prototype.hide = function() {
+  this.elem.style.display = "none";
+  this.background.style.display = "none";
+  window.removeEventListener("resize", this.reposition);
+  document.documentElement.removeEventListener("onscroll", this.reposition);
+}
diff --git a/experimental/palette/tooltip.css b/experimental/palette/tooltip.css
new file mode 100644 (file)
index 0000000..1210e34
--- /dev/null
@@ -0,0 +1,20 @@
+.tooltip {
+  position: absolute;
+  border: 1px solid black;
+  background-color: lightyellow;
+  width: 280px;
+}
+
+.tooltip .prompt {
+  font-family: Inconsolata, Courier New, Courier;
+}
+
+.tooltip .type {
+  font-family: Inconsolata, Courier New, Courier;
+  margin-bottom: 0.5em;
+  font-size: smaller;
+}
+
+.tooltip .body {
+  font-size: smaller;
+}
diff --git a/experimental/palette/tooltip.js b/experimental/palette/tooltip.js
new file mode 100644 (file)
index 0000000..5014934
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (c) 2011 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/** 
+ * @fileoverview Dygraphs options palette tooltip.
+ *
+ * @author konigsberg@google.com (Robert Konigsberg)
+ */
+"use strict";
+
+function Tooltip(parent) {
+  if (!parent) {
+    parent = document.getElementsByTagName("body")[0];
+  }
+  this.elem = Palette.createChild("div", parent);
+  this.title = Palette.createChild("div", this.elem);
+  this.elem.className = "tooltip";
+  this.title.className = "title";
+  this.type = Palette.createChild("div", this.elem);
+  this.type.className = "type";
+  this.body = Palette.createChild("div", this.elem);
+  this.body.className = "body";
+  this.hide();
+}
+
+Tooltip.prototype.show = function(source, event, title, type, body) {
+  this.title.innerHTML = title;
+  this.body.innerHTML = body;
+  this.type.textContent = type; // textContent for arrays.
+
+  var getTopLeft = function(element) {
+    var x = element.offsetLeft;
+    var y = element.offsetTop;
+    element = element.offsetParent;
+
+    while(element != null) {
+      x = parseInt(x) + parseInt(element.offsetLeft);
+      y = parseInt(y) + parseInt(element.offsetTop);
+      element = element.offsetParent;
+    }
+    return [y, x];
+  }
+
+  this.elem.style.height = source.style.height;
+  this.elem.style.width = "280";
+  var topLeft = getTopLeft(source);
+  this.elem.style.top = parseInt(topLeft[0] + source.offsetHeight) + 'px';
+  this.elem.style.left = parseInt(topLeft[1] + 10) + 'px';
+  this.elem.style.visibility = "visible";
+}
+
+Tooltip.prototype.hide = function() {
+  this.elem.style.visibility = "hidden";
+}
diff --git a/jshint/CHANGELOG b/jshint/CHANGELOG
new file mode 100644 (file)
index 0000000..695f0ad
--- /dev/null
@@ -0,0 +1,63 @@
+April 16, 2011
+  * New edition: 2011-04-16
+
+  * Unit tests for all options and some core functions
+  * A number of small typo and message fixes
+  * JSHint is now a JavaScript/JSON parser only
+    (ADSafe, HTML, CSS related code was removed)
+  * JSHint now supports function-scoped options
+  * JSHint now supports both /*jshint and /*jslint
+
+  * JSHint now supports shebangs (#!)
+  * Added support for typed array globals
+  * Allowed the use of variables/functions before definition.
+    (option 'latedef' to disallow)
+  * Fixed a bug with 'forin' option
+  * Fixed Rhino wrapper's CLI options support
+  * Fixed a bug with JSHint leaking internal variables
+  * Added option 'expr' to allow ExpressionStatement as valid Program
+  * Added an option 'prototypejs' to pre-define Prototype globals
+  * Added XPathResult et al. to the 'browser' option
+  * Added support for 'undefined' as a function parameter
+  * Option 'boss' has now precedence over 'eqeqeq' when it comes to '== null'
+  * Fixed a bug with JSHint parsing getters/setters as function statements
+  * Added an option 'mootools' to pre-define MooTools globals
+  * Added an option 'globalstrict' to allow the use of global strict mode
+  * Added HTMLElement to the browser environment
+  * Added support for the void operator
+  * Fixed a bug with 'new Array'
+  * Added option 'white' to check for trailing whitespaces
+
+March 01, 2011
+  * New edition: 2011-03-02
+
+  * When library is used from Rhino, you can provide options via command line arguments
+
+  * Added new HTML5 globals to the 'browser' option
+  * Tolerate == null when boss:true
+  * Tolerate undefined variables in the typeof and delete
+  * Tolerate undefined as a formal parameter
+  * Recognize new Array(<expr>) as a valid expression
+  * Added support for explicit case statement fallthroughs (using special comments)
+  * Added third formal parameter to JSHINT for specifying pre-defined globals
+
+  * New option 'asi' to tolerate the use of automatic semicolon insertion
+  * New option 'jquery' to assume jQuery environment
+  * New option 'couch' to assume CouchDB environment
+
+February 19, 2011
+  * New edition: 2011-02-19
+
+  * Library can act as a Node.js module and a Rhino program
+
+  * Tolerate single statements in if/for/while constructions ('curly' to disallow)
+  * Tolerate arguments.callee and arguments.caller ('noarg' to disallow)
+  * Tolerate empty blocks ('noempty' to disallow)
+  * Tolerate the use of `new` for side-effects ('nonew' to disallow)
+  * Less strict styling check by default ('white' to revert)
+
+  * New option 'boss' to tolerate assignments inside if/for/while
+  * New option 'node' to assume Node environment
+
+January 19, 2011
+  * Forked JSLint from the edition 2010-12-16
\ No newline at end of file
diff --git a/jshint/Makefile b/jshint/Makefile
new file mode 100644 (file)
index 0000000..512ca3b
--- /dev/null
@@ -0,0 +1,16 @@
+build_dir:
+       @mkdir -p "build"
+
+rhino: build_dir
+       @echo "Building JSHint for Rhino"
+       @cat "jshint.js" > "build/jshint-rhino.js" && \
+               cat "env/rhino.js" >> "build/jshint-rhino.js" && \
+               echo "Done"
+
+test:
+       @echo "Running all tests"
+       @expresso tests/*.js
+
+clean:
+       @echo "Cleaning"
+       @rm -f build/*.js && echo "Done"
diff --git a/jshint/README.markdown b/jshint/README.markdown
new file mode 100755 (executable)
index 0000000..d8e6713
--- /dev/null
@@ -0,0 +1,71 @@
+JSHint, A Static Code Analysis Tool for JavaScript
+==================================================
+
+JSHint is a community-driven tool to detect errors and potential problems in
+JavaScript code and to enforce your team's coding conventions.
+
+**IMPORTANT**:
+
+ * This README is for people who are thinking about contributing to JSHint. For general usage
+   please refer to [our website](http://jshint.com/).
+ * If you want to report a bug about the website, please go to the
+   [jshint/site](https://github.com/jshint/site/) repository.
+ * If you want to report a bug or contribute to our NPM package, please go to the
+   [jshint/node-jshint](https://github.com/jshint/node-jshint/) repository.
+
+Reporting a bug
+---------------
+
+To report a bug simply create a [new GitHub Issue](https://github.com/jshint/jshint/issues/new) and
+describe your problem or suggestion. We welcome all kind of feedback regarding JSHint including but
+not limited to:
+
+ * When JSHint doesn't work as expected
+ * When JSHint complains about valid JavaScript code that works in all browsers
+ * When you simply want a new option or feature
+
+Please, before reporting a bug look around to see if there are any open or closed tickets that
+cover your issue. And remember the wisdom: pull request > bug report > tweet.
+
+Submitting patches
+------------------
+
+The best way to make sure your issue is addressed is to submit a patch. GitHub provides a very
+nice interface--pull requests--for that but we accept patches through all mediums: email, issue
+comment, tweet with a link to a snippet, etc.
+
+Before submitting a patch make sure that you comply to our style. We don't have specific style
+guide so just look around the code you are changing.
+
+Also, make sure that you write tests for new features and make sure that all tests pass before
+submitting a patch. Patches that break the build will be rejected.
+
+**FEATURE FREEZE**: Please note that we currently have a feature freeze on new environments and
+styling options. The only patches we accept at this time are for bug fixes.
+
+Tests
+-----
+
+To run tests you will need to install [node.js](http://nodejs.org/) and
+expresso. You can install the latter with npm:
+
+    npm install expresso
+
+After that, running tests is as easy as:
+
+    expresso tests/*.js
+
+Attribution
+-----------
+
+Maintainer: [Anton Kovalyov](http://anton.kovalyov.net/) ([@valueof](http://twitter.com/valueof))
+
+Distinguished Contributors:
+
+ * [Wolfgang Kluge](http://klugesoftware.de/) ([blog](http://gehirnwindung.de/))
+ * [Josh Perez](http://www.goatslacker.com/)
+
+Thank you!
+----------
+
+We really appreciate all kind of feedback and contributions. Thanks for using and supporing JSHint!
\ No newline at end of file
diff --git a/jshint/build/jshint-rhino.js b/jshint/build/jshint-rhino.js
new file mode 100644 (file)
index 0000000..055d38c
--- /dev/null
@@ -0,0 +1,4389 @@
+/*!
+ * JSHint, by JSHint Community.
+ *
+ * Licensed under the same slightly modified MIT license that JSLint is.
+ * It stops evil-doers everywhere.
+ *
+ * JSHint is a derivative work of JSLint:
+ *
+ *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
+ *
+ *   Permission is hereby granted, free of charge, to any person obtaining
+ *   a copy of this software and associated documentation files (the "Software"),
+ *   to deal in the Software without restriction, including without limitation
+ *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ *   and/or sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following conditions:
+ *
+ *   The above copyright notice and this permission notice shall be included
+ *   in all copies or substantial portions of the Software.
+ *
+ *   The Software shall be used for Good, not Evil.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ *   DEALINGS IN THE SOFTWARE.
+ *
+ * JSHint was forked from 2010-12-16 edition of JSLint.
+ *
+ */
+
+/*
+ JSHINT is a global function. It takes two parameters.
+
+     var myResult = JSHINT(source, option);
+
+ The first parameter is either a string or an array of strings. If it is a
+ string, it will be split on '\n' or '\r'. If it is an array of strings, it
+ is assumed that each string represents one line. The source can be a
+ JavaScript text or a JSON text.
+
+ The second parameter is an optional object of options which control the
+ operation of JSHINT. Most of the options are booleans: They are all
+ optional and have a default value of false. One of the options, predef,
+ can be an array of names, which will be used to declare global variables,
+ or an object whose keys are used as global names, with a boolean value
+ that determines if they are assignable.
+
+ If it checks out, JSHINT returns true. Otherwise, it returns false.
+
+ If false, you can inspect JSHINT.errors to find out the problems.
+ JSHINT.errors is an array of objects containing these members:
+
+ {
+     line      : The line (relative to 0) at which the lint was found
+     character : The character (relative to 0) at which the lint was found
+     reason    : The problem
+     evidence  : The text line in which the problem occurred
+     raw       : The raw message before the details were inserted
+     a         : The first detail
+     b         : The second detail
+     c         : The third detail
+     d         : The fourth detail
+ }
+
+ If a fatal error was found, a null will be the last element of the
+ JSHINT.errors array.
+
+ You can request a Function Report, which shows all of the functions
+ and the parameters and vars that they use. This can be used to find
+ implied global variables and other problems. The report is in HTML and
+ can be inserted in an HTML <body>.
+
+     var myReport = JSHINT.report(limited);
+
+ If limited is true, then the report will be limited to only errors.
+
+ You can request a data structure which contains JSHint's results.
+
+     var myData = JSHINT.data();
+
+ It returns a structure with this form:
+
+ {
+     errors: [
+         {
+             line: NUMBER,
+             character: NUMBER,
+             reason: STRING,
+             evidence: STRING
+         }
+     ],
+     functions: [
+         name: STRING,
+         line: NUMBER,
+         last: NUMBER,
+         param: [
+             STRING
+         ],
+         closure: [
+             STRING
+         ],
+         var: [
+             STRING
+         ],
+         exception: [
+             STRING
+         ],
+         outer: [
+             STRING
+         ],
+         unused: [
+             STRING
+         ],
+         global: [
+             STRING
+         ],
+         label: [
+             STRING
+         ]
+     ],
+     globals: [
+         STRING
+     ],
+     member: {
+         STRING: NUMBER
+     },
+     unuseds: [
+         {
+             name: STRING,
+             line: NUMBER
+         }
+     ],
+     implieds: [
+         {
+             name: STRING,
+             line: NUMBER
+         }
+     ],
+     urls: [
+         STRING
+     ],
+     json: BOOLEAN
+ }
+
+ Empty arrays will not be included.
+
+*/
+
+/*jshint
+ evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true,
+ undef: true, maxlen: 100, indent:4
+*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)",
+ "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)",
+ "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)",
+ "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==",
+ "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax,
+ __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio,
+ Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas,
+ CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date,
+ Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag,
+ E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event,
+ Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form,
+ FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey,
+ HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement,
+ HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement,
+ HTMLDivElement, HTMLDListElement, HTMLFieldSetElement,
+ HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement,
+ HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement,
+ HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement,
+ HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement,
+ HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement,
+ HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement,
+ HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement,
+ HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement,
+ HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement,
+ HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement,
+ HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement,
+ Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array,
+ Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E,
+ MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native,
+ NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI,
+ POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError,
+ Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation,
+ SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion,
+ ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller,
+ Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables,
+ SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template,
+ Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL,
+ VBArray, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator,
+ XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a,
+ addEventListener, address, alert, apply, applicationCache, arguments, arity,
+ asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee,
+ caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout,
+ close, closed, closure, comment, condition, confirm, console, constructor,
+ content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI,
+ decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document,
+ dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent,
+ entityify, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil,
+ ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus,
+ forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions,
+ g, gc, getComputedStyle, getRow, getter, GLOBAL, global, globals, globalstrict,
+ hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include,
+ indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray,
+ isDigit, isFinite, isNaN, iterator, java, join, jshint,
+ JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak,
+ latedef, lbp, led, left, length, line, load, loadClass, localStorage, location,
+ log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy,
+ moveTo, mootools, multistr, name, navigator, new, newcap, noarg, node, noempty, nomen,
+ nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus,
+ onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param,
+ parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt,
+ proto, prototype, prototypejs, provides, push, quit, range, raw, reach, reason, regexp,
+ readFile, readUrl, regexdash, removeEventListener, replace, report, require,
+ reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right,
+ runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal,
+ send, serialize, sessionStorage, setInterval, setTimeout, setter, setterToken, shift, slice,
+ smarttabs, sort, spawn, split, stack, status, start, strict, sub, substr, supernew, shadow,
+ supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing,
+ type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef, undefs, unused, urls, validthis,
+ value, valueOf, var, version, WebSocket, white, window, Worker, wsh*/
+
+/*global exports: false */
+
+// We build the application inside a function so that we produce only a single
+// global variable. That function will be invoked immediately, and its return
+// value is the JSHINT function itself.
+
+var JSHINT = (function () {
+    "use strict";
+
+    var anonname,       // The guessed name for anonymous functions.
+
+// These are operators that should not be used with the ! operator.
+
+        bang = {
+            '<'  : true,
+            '<=' : true,
+            '==' : true,
+            '===': true,
+            '!==': true,
+            '!=' : true,
+            '>'  : true,
+            '>=' : true,
+            '+'  : true,
+            '-'  : true,
+            '*'  : true,
+            '/'  : true,
+            '%'  : true
+        },
+
+        // These are the JSHint boolean options.
+        boolOptions = {
+            asi         : true, // if automatic semicolon insertion should be tolerated
+            bitwise     : true, // if bitwise operators should not be allowed
+            boss        : true, // if advanced usage of assignments should be allowed
+            browser     : true, // if the standard browser globals should be predefined
+            couch       : true, // if CouchDB globals should be predefined
+            curly       : true, // if curly braces around all blocks should be required
+            debug       : true, // if debugger statements should be allowed
+            devel       : true, // if logging globals should be predefined (console,
+                                // alert, etc.)
+            dojo        : true, // if Dojo Toolkit globals should be predefined
+            eqeqeq      : true, // if === should be required
+            eqnull      : true, // if == null comparisons should be tolerated
+            es5         : true, // if ES5 syntax should be allowed
+            esnext      : true, // if es.next specific syntax should be allowed
+            evil        : true, // if eval should be allowed
+            expr        : true, // if ExpressionStatement should be allowed as Programs
+            forin       : true, // if for in statements must filter
+            funcscope   : true, // if only function scope should be used for scope tests
+            globalstrict: true, // if global "use strict"; should be allowed (also
+                                // enables 'strict')
+            immed       : true, // if immediate invocations must be wrapped in parens
+            iterator    : true, // if the `__iterator__` property should be allowed
+            jquery      : true, // if jQuery globals should be predefined
+            lastsemic   : true, // if semicolons may be ommitted for the trailing
+                                // statements inside of a one-line blocks.
+            latedef     : true, // if the use before definition should not be tolerated
+            laxbreak    : true, // if line breaks should not be checked
+            loopfunc    : true, // if functions should be allowed to be defined within
+                                // loops
+            mootools    : true, // if MooTools globals should be predefined
+            multistr    : true, // allow multiline strings
+            newcap      : true, // if constructor names must be capitalized
+            noarg       : true, // if arguments.caller and arguments.callee should be
+                                // disallowed
+            node        : true, // if the Node.js environment globals should be
+                                // predefined
+            noempty     : true, // if empty blocks should be disallowed
+            nonew       : true, // if using `new` for side-effects should be disallowed
+            nonstandard : true, // if non-standard (but widely adopted) globals should
+                                // be predefined
+            nomen       : true, // if names should be checked
+            onevar      : true, // if only one var statement per function should be
+                                // allowed
+            onecase     : true, // if one case switch statements should be allowed
+            passfail    : true, // if the scan should stop on first error
+            plusplus    : true, // if increment/decrement should not be allowed
+            proto       : true, // if the `__proto__` property should be allowed
+            prototypejs : true, // if Prototype and Scriptaculous globals should be
+                                // predefined
+            regexdash   : true, // if unescaped first/last dash (-) inside brackets
+                                // should be tolerated
+            regexp      : true, // if the . should not be allowed in regexp literals
+            rhino       : true, // if the Rhino environment globals should be predefined
+            undef       : true, // if variables should be declared before used
+            scripturl   : true, // if script-targeted URLs should be tolerated
+            shadow      : true, // if variable shadowing should be tolerated
+            smarttabs   : true, // if smarttabs should be tolerated
+                                // (http://www.emacswiki.org/emacs/SmartTabs)
+            strict      : true, // require the "use strict"; pragma
+            sub         : true, // if all forms of subscript notation are tolerated
+            supernew    : true, // if `new function () { ... };` and `new Object;`
+                                // should be tolerated
+            trailing    : true, // if trailing whitespace rules apply
+            validthis   : true, // if 'this' inside a non-constructor function is valid.
+                                // This is a function scoped option only.
+            white       : true, // if strict whitespace rules apply
+            wsh         : true  // if the Windows Scripting Host environment globals
+                                // should be predefined
+        },
+
+        // browser contains a set of global names which are commonly provided by a
+        // web browser environment.
+        browser = {
+            ArrayBuffer              :  false,
+            ArrayBufferView          :  false,
+            Audio                    :  false,
+            addEventListener         :  false,
+            applicationCache         :  false,
+            blur                     :  false,
+            clearInterval            :  false,
+            clearTimeout             :  false,
+            close                    :  false,
+            closed                   :  false,
+            DataView                 :  false,
+            defaultStatus            :  false,
+            document                 :  false,
+            event                    :  false,
+            FileReader               :  false,
+            Float32Array             :  false,
+            Float64Array             :  false,
+            FormData                 :  false,
+            focus                    :  false,
+            frames                   :  false,
+            getComputedStyle         :  false,
+            HTMLElement              :  false,
+            HTMLAnchorElement        :  false,
+            HTMLBaseElement          :  false,
+            HTMLBlockquoteElement    :  false,
+            HTMLBodyElement          :  false,
+            HTMLBRElement            :  false,
+            HTMLButtonElement        :  false,
+            HTMLCanvasElement        :  false,
+            HTMLDirectoryElement     :  false,
+            HTMLDivElement           :  false,
+            HTMLDListElement         :  false,
+            HTMLFieldSetElement      :  false,
+            HTMLFontElement          :  false,
+            HTMLFormElement          :  false,
+            HTMLFrameElement         :  false,
+            HTMLFrameSetElement      :  false,
+            HTMLHeadElement          :  false,
+            HTMLHeadingElement       :  false,
+            HTMLHRElement            :  false,
+            HTMLHtmlElement          :  false,
+            HTMLIFrameElement        :  false,
+            HTMLImageElement         :  false,
+            HTMLInputElement         :  false,
+            HTMLIsIndexElement       :  false,
+            HTMLLabelElement         :  false,
+            HTMLLayerElement         :  false,
+            HTMLLegendElement        :  false,
+            HTMLLIElement            :  false,
+            HTMLLinkElement          :  false,
+            HTMLMapElement           :  false,
+            HTMLMenuElement          :  false,
+            HTMLMetaElement          :  false,
+            HTMLModElement           :  false,
+            HTMLObjectElement        :  false,
+            HTMLOListElement         :  false,
+            HTMLOptGroupElement      :  false,
+            HTMLOptionElement        :  false,
+            HTMLParagraphElement     :  false,
+            HTMLParamElement         :  false,
+            HTMLPreElement           :  false,
+            HTMLQuoteElement         :  false,
+            HTMLScriptElement        :  false,
+            HTMLSelectElement        :  false,
+            HTMLStyleElement         :  false,
+            HTMLTableCaptionElement  :  false,
+            HTMLTableCellElement     :  false,
+            HTMLTableColElement      :  false,
+            HTMLTableElement         :  false,
+            HTMLTableRowElement      :  false,
+            HTMLTableSectionElement  :  false,
+            HTMLTextAreaElement      :  false,
+            HTMLTitleElement         :  false,
+            HTMLUListElement         :  false,
+            HTMLVideoElement         :  false,
+            history                  :  false,
+            Int16Array               :  false,
+            Int32Array               :  false,
+            Int8Array                :  false,
+            Image                    :  false,
+            length                   :  false,
+            localStorage             :  false,
+            location                 :  false,
+            moveBy                   :  false,
+            moveTo                   :  false,
+            name                     :  false,
+            navigator                :  false,
+            onbeforeunload           :  true,
+            onblur                   :  true,
+            onerror                  :  true,
+            onfocus                  :  true,
+            onload                   :  true,
+            onresize                 :  true,
+            onunload                 :  true,
+            open                     :  false,
+            openDatabase             :  false,
+            opener                   :  false,
+            Option                   :  false,
+            parent                   :  false,
+            print                    :  false,
+            removeEventListener      :  false,
+            resizeBy                 :  false,
+            resizeTo                 :  false,
+            screen                   :  false,
+            scroll                   :  false,
+            scrollBy                 :  false,
+            scrollTo                 :  false,
+            sessionStorage           :  false,
+            setInterval              :  false,
+            setTimeout               :  false,
+            SharedWorker             :  false,
+            status                   :  false,
+            top                      :  false,
+            Uint16Array              :  false,
+            Uint32Array              :  false,
+            Uint8Array               :  false,
+            WebSocket                :  false,
+            window                   :  false,
+            Worker                   :  false,
+            XMLHttpRequest           :  false,
+            XPathEvaluator           :  false,
+            XPathException           :  false,
+            XPathExpression          :  false,
+            XPathNamespace           :  false,
+            XPathNSResolver          :  false,
+            XPathResult              :  false
+        },
+
+        couch = {
+            "require" : false,
+            respond   : false,
+            getRow    : false,
+            emit      : false,
+            send      : false,
+            start     : false,
+            sum       : false,
+            log       : false,
+            exports   : false,
+            module    : false,
+            provides  : false
+        },
+
+        devel = {
+            alert   : false,
+            confirm : false,
+            console : false,
+            Debug   : false,
+            opera   : false,
+            prompt  : false
+        },
+
+        dojo = {
+            dojo      : false,
+            dijit     : false,
+            dojox     : false,
+            define    : false,
+            "require" : false
+        },
+
+        escapes = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '/' : '\\/',
+            '\\': '\\\\'
+        },
+
+        funct,          // The current function
+
+        functionicity = [
+            'closure', 'exception', 'global', 'label',
+            'outer', 'unused', 'var'
+        ],
+
+        functions,      // All of the functions
+
+        global,         // The global scope
+        implied,        // Implied globals
+        inblock,
+        indent,
+        jsonmode,
+
+        jquery = {
+            '$'    : false,
+            jQuery : false
+        },
+
+        lines,
+        lookahead,
+        member,
+        membersOnly,
+
+        mootools = {
+            '$'             : false,
+            '$$'            : false,
+            Assets          : false,
+            Browser         : false,
+            Chain           : false,
+            Class           : false,
+            Color           : false,
+            Cookie          : false,
+            Core            : false,
+            Document        : false,
+            DomReady        : false,
+            DOMReady        : false,
+            Drag            : false,
+            Element         : false,
+            Elements        : false,
+            Event           : false,
+            Events          : false,
+            Fx              : false,
+            Group           : false,
+            Hash            : false,
+            HtmlTable       : false,
+            Iframe          : false,
+            IframeShim      : false,
+            InputValidator  : false,
+            instanceOf      : false,
+            Keyboard        : false,
+            Locale          : false,
+            Mask            : false,
+            MooTools        : false,
+            Native          : false,
+            Options         : false,
+            OverText        : false,
+            Request         : false,
+            Scroller        : false,
+            Slick           : false,
+            Slider          : false,
+            Sortables       : false,
+            Spinner         : false,
+            Swiff           : false,
+            Tips            : false,
+            Type            : false,
+            typeOf          : false,
+            URI             : false,
+            Window          : false
+        },
+
+        nexttoken,
+
+        node = {
+            __filename    : false,
+            __dirname     : false,
+            Buffer        : false,
+            console       : false,
+            exports       : false,
+            GLOBAL        : false,
+            global        : false,
+            module        : false,
+            process       : false,
+            require       : false,
+            setTimeout    : false,
+            clearTimeout  : false,
+            setInterval   : false,
+            clearInterval : false
+        },
+
+        noreach,
+        option,
+        predefined,     // Global variables defined by option
+        prereg,
+        prevtoken,
+
+        prototypejs = {
+            '$'               : false,
+            '$$'              : false,
+            '$A'              : false,
+            '$F'              : false,
+            '$H'              : false,
+            '$R'              : false,
+            '$break'          : false,
+            '$continue'       : false,
+            '$w'              : false,
+            Abstract          : false,
+            Ajax              : false,
+            Class             : false,
+            Enumerable        : false,
+            Element           : false,
+            Event             : false,
+            Field             : false,
+            Form              : false,
+            Hash              : false,
+            Insertion         : false,
+            ObjectRange       : false,
+            PeriodicalExecuter: false,
+            Position          : false,
+            Prototype         : false,
+            Selector          : false,
+            Template          : false,
+            Toggle            : false,
+            Try               : false,
+            Autocompleter     : false,
+            Builder           : false,
+            Control           : false,
+            Draggable         : false,
+            Draggables        : false,
+            Droppables        : false,
+            Effect            : false,
+            Sortable          : false,
+            SortableObserver  : false,
+            Sound             : false,
+            Scriptaculous     : false
+        },
+
+        rhino = {
+            defineClass  : false,
+            deserialize  : false,
+            gc           : false,
+            help         : false,
+            importPackage: false,
+            "java"       : false,
+            load         : false,
+            loadClass    : false,
+            print        : false,
+            quit         : false,
+            readFile     : false,
+            readUrl      : false,
+            runCommand   : false,
+            seal         : false,
+            serialize    : false,
+            spawn        : false,
+            sync         : false,
+            toint32      : false,
+            version      : false
+        },
+
+        scope,      // The current scope
+        stack,
+
+        // standard contains the global names that are provided by the
+        // ECMAScript standard.
+        standard = {
+            Array               : false,
+            Boolean             : false,
+            Date                : false,
+            decodeURI           : false,
+            decodeURIComponent  : false,
+            encodeURI           : false,
+            encodeURIComponent  : false,
+            Error               : false,
+            'eval'              : false,
+            EvalError           : false,
+            Function            : false,
+            hasOwnProperty      : false,
+            isFinite            : false,
+            isNaN               : false,
+            JSON                : false,
+            Math                : false,
+            Number              : false,
+            Object              : false,
+            parseInt            : false,
+            parseFloat          : false,
+            RangeError          : false,
+            ReferenceError      : false,
+            RegExp              : false,
+            String              : false,
+            SyntaxError         : false,
+            TypeError           : false,
+            URIError            : false
+        },
+
+        // widely adopted global names that are not part of ECMAScript standard
+        nonstandard = {
+            escape              : false,
+            unescape            : false
+        },
+
+        standard_member = {
+            E                   : true,
+            LN2                 : true,
+            LN10                : true,
+            LOG2E               : true,
+            LOG10E              : true,
+            MAX_VALUE           : true,
+            MIN_VALUE           : true,
+            NEGATIVE_INFINITY   : true,
+            PI                  : true,
+            POSITIVE_INFINITY   : true,
+            SQRT1_2             : true,
+            SQRT2               : true
+        },
+
+        directive,
+        syntax = {},
+        tab,
+        token,
+        urls,
+        useESNextSyntax,
+        warnings,
+
+        wsh = {
+            ActiveXObject             : true,
+            Enumerator                : true,
+            GetObject                 : true,
+            ScriptEngine              : true,
+            ScriptEngineBuildVersion  : true,
+            ScriptEngineMajorVersion  : true,
+            ScriptEngineMinorVersion  : true,
+            VBArray                   : true,
+            WSH                       : true,
+            WScript                   : true,
+            XDomainRequest            : true
+        };
+
+    // Regular expressions. Some of these are stupidly long.
+    var ax, cx, tx, nx, nxg, lx, ix, jx, ft;
+    (function () {
+        /*jshint maxlen:300 */
+
+        // unsafe comment or string
+        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
+
+        // unsafe characters that are silently deleted by one or more browsers
+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+
+        // token
+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/;
+
+        // characters in strings that need escapement
+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+
+        // star slash
+        lx = /\*\/|\/\*/;
+
+        // identifier
+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
+
+        // javascript url
+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
+
+        // catches /* falls through */ comments
+        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;
+    }());
+
+    function F() {}     // Used by Object.create
+
+    function is_own(object, name) {
+
+// The object.hasOwnProperty method fails when the property under consideration
+// is named 'hasOwnProperty'. So we have to use this more convoluted form.
+
+        return Object.prototype.hasOwnProperty.call(object, name);
+    }
+
+// Provide critical ES5 functions to ES3.
+
+    if (typeof Array.isArray !== 'function') {
+        Array.isArray = function (o) {
+            return Object.prototype.toString.apply(o) === '[object Array]';
+        };
+    }
+
+    if (typeof Object.create !== 'function') {
+        Object.create = function (o) {
+            F.prototype = o;
+            return new F();
+        };
+    }
+
+    if (typeof Object.keys !== 'function') {
+        Object.keys = function (o) {
+            var a = [], k;
+            for (k in o) {
+                if (is_own(o, k)) {
+                    a.push(k);
+                }
+            }
+            return a;
+        };
+    }
+
+// Non standard methods
+
+    if (typeof String.prototype.entityify !== 'function') {
+        String.prototype.entityify = function () {
+            return this
+                .replace(/&/g, '&amp;')
+                .replace(/</g, '&lt;')
+                .replace(/>/g, '&gt;');
+        };
+    }
+
+    if (typeof String.prototype.isAlpha !== 'function') {
+        String.prototype.isAlpha = function () {
+            return (this >= 'a' && this <= 'z\uffff') ||
+                (this >= 'A' && this <= 'Z\uffff');
+        };
+    }
+
+    if (typeof String.prototype.isDigit !== 'function') {
+        String.prototype.isDigit = function () {
+            return (this >= '0' && this <= '9');
+        };
+    }
+
+    if (typeof String.prototype.supplant !== 'function') {
+        String.prototype.supplant = function (o) {
+            return this.replace(/\{([^{}]*)\}/g, function (a, b) {
+                var r = o[b];
+                return typeof r === 'string' || typeof r === 'number' ? r : a;
+            });
+        };
+    }
+
+    if (typeof String.prototype.name !== 'function') {
+        String.prototype.name = function () {
+
+// If the string looks like an identifier, then we can return it as is.
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can simply slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe
+// sequences.
+
+            if (ix.test(this)) {
+                return this;
+            }
+            if (nx.test(this)) {
+                return '"' + this.replace(nxg, function (a) {
+                    var c = escapes[a];
+                    if (c) {
+                        return c;
+                    }
+                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
+                }) + '"';
+            }
+            return '"' + this + '"';
+        };
+    }
+
+
+    function combine(t, o) {
+        var n;
+        for (n in o) {
+            if (is_own(o, n)) {
+                t[n] = o[n];
+            }
+        }
+    }
+
+    function assume() {
+        if (option.couch) {
+            combine(predefined, couch);
+        }
+
+        if (option.rhino) {
+            combine(predefined, rhino);
+        }
+
+        if (option.prototypejs) {
+            combine(predefined, prototypejs);
+        }
+
+        if (option.node) {
+            combine(predefined, node);
+        }
+
+        if (option.devel) {
+            combine(predefined, devel);
+        }
+
+        if (option.dojo) {
+            combine(predefined, dojo);
+        }
+
+        if (option.browser) {
+            combine(predefined, browser);
+        }
+
+        if (option.nonstandard) {
+            combine(predefined, nonstandard);
+        }
+
+        if (option.jquery) {
+            combine(predefined, jquery);
+        }
+
+        if (option.mootools) {
+            combine(predefined, mootools);
+        }
+
+        if (option.wsh) {
+            combine(predefined, wsh);
+        }
+
+        if (option.esnext) {
+            useESNextSyntax();
+        }
+
+        if (option.globalstrict && option.strict !== false) {
+            option.strict = true;
+        }
+    }
+
+
+    // Produce an error warning.
+    function quit(message, line, chr) {
+        var percentage = Math.floor((line / lines.length) * 100);
+
+        throw {
+            name: 'JSHintError',
+            line: line,
+            character: chr,
+            message: message + " (" + percentage + "% scanned).",
+            raw: message
+        };
+    }
+
+    function isundef(scope, m, t, a) {
+        return JSHINT.undefs.push([scope, m, t, a]);
+    }
+
+    function warning(m, t, a, b, c, d) {
+        var ch, l, w;
+        t = t || nexttoken;
+        if (t.id === '(end)') {  // `~
+            t = token;
+        }
+        l = t.line || 0;
+        ch = t.from || 0;
+        w = {
+            id: '(error)',
+            raw: m,
+            evidence: lines[l - 1] || '',
+            line: l,
+            character: ch,
+            a: a,
+            b: b,
+            c: c,
+            d: d
+        };
+        w.reason = m.supplant(w);
+        JSHINT.errors.push(w);
+        if (option.passfail) {
+            quit('Stopping. ', l, ch);
+        }
+        warnings += 1;
+        if (warnings >= option.maxerr) {
+            quit("Too many errors.", l, ch);
+        }
+        return w;
+    }
+
+    function warningAt(m, l, ch, a, b, c, d) {
+        return warning(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+    function error(m, t, a, b, c, d) {
+        var w = warning(m, t, a, b, c, d);
+    }
+
+    function errorAt(m, l, ch, a, b, c, d) {
+        return error(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+
+
+// lexical analysis and token construction
+
+    var lex = (function lex() {
+        var character, from, line, s;
+
+// Private lex methods
+
+        function nextLine() {
+            var at,
+                tw; // trailing whitespace check
+
+            if (line >= lines.length)
+                return false;
+
+            character = 1;
+            s = lines[line];
+            line += 1;
+
+            // If smarttabs option is used check for spaces followed by tabs only.
+            // Otherwise check for any occurence of mixed tabs and spaces.
+            if (option.smarttabs)
+                at = s.search(/ \t/);
+            else
+                at = s.search(/ \t|\t /);
+
+            if (at >= 0)
+                warningAt("Mixed spaces and tabs.", line, at + 1);
+
+            s = s.replace(/\t/g, tab);
+            at = s.search(cx);
+
+            if (at >= 0)
+                warningAt("Unsafe character.", line, at);
+
+            if (option.maxlen && option.maxlen < s.length)
+                warningAt("Line too long.", line, s.length);
+
+            // Check for trailing whitespaces
+            tw = /\s+$/.test(s);
+            if (option.trailing && tw && !/^\s+$/.test(s)) {
+                warningAt("Trailing whitespace.", line, tw);
+            }
+            return true;
+        }
+
+// Produce a token object.  The token inherits from a syntax symbol.
+
+        function it(type, value) {
+            var i, t;
+            if (type === '(color)' || type === '(range)') {
+                t = {type: type};
+            } else if (type === '(punctuator)' ||
+                    (type === '(identifier)' && is_own(syntax, value))) {
+                t = syntax[value] || syntax['(error)'];
+            } else {
+                t = syntax[type];
+            }
+            t = Object.create(t);
+            if (type === '(string)' || type === '(range)') {
+                if (!option.scripturl && jx.test(value)) {
+                    warningAt("Script URL.", line, from);
+                }
+            }
+            if (type === '(identifier)') {
+                t.identifier = true;
+                if (value === '__proto__' && !option.proto) {
+                    warningAt("The '{a}' property is deprecated.",
+                        line, from, value);
+                } else if (value === '__iterator__' && !option.iterator) {
+                    warningAt("'{a}' is only available in JavaScript 1.7.",
+                        line, from, value);
+                } else if (option.nomen && (value.charAt(0) === '_' ||
+                         value.charAt(value.length - 1) === '_')) {
+                    if (!option.node || token.id === '.' ||
+                            (value !== '__dirname' && value !== '__filename')) {
+                        warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value);
+                    }
+                }
+            }
+            t.value = value;
+            t.line = line;
+            t.character = character;
+            t.from = from;
+            i = t.id;
+            if (i !== '(endline)') {
+                prereg = i &&
+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
+                    i === 'return' ||
+                    i === 'case');
+            }
+            return t;
+        }
+
+        // Public lex methods
+        return {
+            init: function (source) {
+                if (typeof source === 'string') {
+                    lines = source
+                        .replace(/\r\n/g, '\n')
+                        .replace(/\r/g, '\n')
+                        .split('\n');
+                } else {
+                    lines = source;
+                }
+
+                // If the first line is a shebang (#!), make it a blank and move on.
+                // Shebangs are used by Node scripts.
+                if (lines[0] && lines[0].substr(0, 2) === '#!')
+                    lines[0] = '';
+
+                line = 0;
+                nextLine();
+                from = 1;
+            },
+
+            range: function (begin, end) {
+                var c, value = '';
+                from = character;
+                if (s.charAt(0) !== begin) {
+                    errorAt("Expected '{a}' and instead saw '{b}'.",
+                            line, character, begin, s.charAt(0));
+                }
+                for (;;) {
+                    s = s.slice(1);
+                    character += 1;
+                    c = s.charAt(0);
+                    switch (c) {
+                    case '':
+                        errorAt("Missing '{a}'.", line, character, c);
+                        break;
+                    case end:
+                        s = s.slice(1);
+                        character += 1;
+                        return it('(range)', value);
+                    case '\\':
+                        warningAt("Unexpected '{a}'.", line, character, c);
+                    }
+                    value += c;
+                }
+
+            },
+
+
+            // token -- this is called by advance to get the next token
+            token: function () {
+                var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange;
+
+                function match(x) {
+                    var r = x.exec(s), r1;
+                    if (r) {
+                        l = r[0].length;
+                        r1 = r[1];
+                        c = r1.charAt(0);
+                        s = s.substr(l);
+                        from = character + l - r1.length;
+                        character += l;
+                        return r1;
+                    }
+                }
+
+                function string(x) {
+                    var c, j, r = '', allowNewLine = false;
+
+                    if (jsonmode && x !== '"') {
+                        warningAt("Strings must use doublequote.",
+                                line, character);
+                    }
+
+                    function esc(n) {
+                        var i = parseInt(s.substr(j + 1, n), 16);
+                        j += n;
+                        if (i >= 32 && i <= 126 &&
+                                i !== 34 && i !== 92 && i !== 39) {
+                            warningAt("Unnecessary escapement.", line, character);
+                        }
+                        character += n;
+                        c = String.fromCharCode(i);
+                    }
+                    j = 0;
+unclosedString:     for (;;) {
+                        while (j >= s.length) {
+                            j = 0;
+
+                            var cl = line, cf = from;
+                            if (!nextLine()) {
+                                errorAt("Unclosed string.", cl, cf);
+                                break unclosedString;
+                            }
+
+                            if (allowNewLine) {
+                                allowNewLine = false;
+                            } else {
+                                warningAt("Unclosed string.", cl, cf);
+                            }
+                        }
+                        c = s.charAt(j);
+                        if (c === x) {
+                            character += 1;
+                            s = s.substr(j + 1);
+                            return it('(string)', r, x);
+                        }
+                        if (c < ' ') {
+                            if (c === '\n' || c === '\r') {
+                                break;
+                            }
+                            warningAt("Control character in string: {a}.",
+                                    line, character + j, s.slice(0, j));
+                        } else if (c === '\\') {
+                            j += 1;
+                            character += 1;
+                            c = s.charAt(j);
+                            switch (c) {
+                            case '\\':
+                            case '"':
+                            case '/':
+                                break;
+                            case '\'':
+                                if (jsonmode) {
+                                    warningAt("Avoid \\'.", line, character);
+                                }
+                                break;
+                            case 'b':
+                                c = '\b';
+                                break;
+                            case 'f':
+                                c = '\f';
+                                break;
+                            case 'n':
+                                c = '\n';
+                                break;
+                            case 'r':
+                                c = '\r';
+                                break;
+                            case 't':
+                                c = '\t';
+                                break;
+                            case 'u':
+                                esc(4);
+                                break;
+                            case 'v':
+                                if (jsonmode) {
+                                    warningAt("Avoid \\v.", line, character);
+                                }
+                                c = '\v';
+                                break;
+                            case 'x':
+                                if (jsonmode) {
+                                    warningAt("Avoid \\x-.", line, character);
+                                }
+                                esc(2);
+                                break;
+                            case '':
+                                // last character is escape character
+                                // always allow new line if escaped, but show
+                                // warning if option is not set
+                                allowNewLine = true;
+                                if (option.multistr) {
+                                    if (jsonmode) {
+                                        warningAt("Avoid EOL escapement.", line, character);
+                                    }
+                                    c = '';
+                                    character -= 1;
+                                    break;
+                                }
+                                warningAt("Bad escapement of EOL. Use option multistr if needed.",
+                                    line, character);
+                                break;
+                            default:
+                                warningAt("Bad escapement.", line, character);
+                            }
+                        }
+                        r += c;
+                        character += 1;
+                        j += 1;
+                    }
+                }
+
+                for (;;) {
+                    if (!s) {
+                        return it(nextLine() ? '(endline)' : '(end)', '');
+                    }
+                    t = match(tx);
+                    if (!t) {
+                        t = '';
+                        c = '';
+                        while (s && s < '!') {
+                            s = s.substr(1);
+                        }
+                        if (s) {
+                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));
+                            s = '';
+                        }
+                    } else {
+
+    //      identifier
+
+                        if (c.isAlpha() || c === '_' || c === '$') {
+                            return it('(identifier)', t);
+                        }
+
+    //      number
+
+                        if (c.isDigit()) {
+                            if (!isFinite(Number(t))) {
+                                warningAt("Bad number '{a}'.",
+                                    line, character, t);
+                            }
+                            if (s.substr(0, 1).isAlpha()) {
+                                warningAt("Missing space after '{a}'.",
+                                        line, character, t);
+                            }
+                            if (c === '0') {
+                                d = t.substr(1, 1);
+                                if (d.isDigit()) {
+                                    if (token.id !== '.') {
+                                        warningAt("Don't use extra leading zeros '{a}'.",
+                                            line, character, t);
+                                    }
+                                } else if (jsonmode && (d === 'x' || d === 'X')) {
+                                    warningAt("Avoid 0x-. '{a}'.",
+                                            line, character, t);
+                                }
+                            }
+                            if (t.substr(t.length - 1) === '.') {
+                                warningAt(
+"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);
+                            }
+                            return it('(number)', t);
+                        }
+                        switch (t) {
+
+    //      string
+
+                        case '"':
+                        case "'":
+                            return string(t);
+
+    //      // comment
+
+                        case '//':
+                            s = '';
+                            token.comment = true;
+                            break;
+
+    //      /* comment
+
+                        case '/*':
+                            for (;;) {
+                                i = s.search(lx);
+                                if (i >= 0) {
+                                    break;
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed comment.", line, character);
+                                }
+                            }
+                            character += i + 2;
+                            if (s.substr(i, 1) === '/') {
+                                errorAt("Nested comment.", line, character);
+                            }
+                            s = s.substr(i + 2);
+                            token.comment = true;
+                            break;
+
+    //      /*members /*jshint /*global
+
+                        case '/*members':
+                        case '/*member':
+                        case '/*jshint':
+                        case '/*jslint':
+                        case '/*global':
+                        case '*/':
+                            return {
+                                value: t,
+                                type: 'special',
+                                line: line,
+                                character: character,
+                                from: from
+                            };
+
+                        case '':
+                            break;
+    //      /
+                        case '/':
+                            if (token.id === '/=') {
+                                errorAt("A regular expression literal can be confused with '/='.",
+                                    line, from);
+                            }
+                            if (prereg) {
+                                depth = 0;
+                                captures = 0;
+                                l = 0;
+                                for (;;) {
+                                    b = true;
+                                    c = s.charAt(l);
+                                    l += 1;
+                                    switch (c) {
+                                    case '':
+                                        errorAt("Unclosed regular expression.", line, from);
+                                        return quit('Stopping.', line, from);
+                                    case '/':
+                                        if (depth > 0) {
+                                            warningAt("{a} unterminated regular expression " +
+                                                "group(s).", line, from + l, depth);
+                                        }
+                                        c = s.substr(0, l - 1);
+                                        q = {
+                                            g: true,
+                                            i: true,
+                                            m: true
+                                        };
+                                        while (q[s.charAt(l)] === true) {
+                                            q[s.charAt(l)] = false;
+                                            l += 1;
+                                        }
+                                        character += l;
+                                        s = s.substr(l);
+                                        q = s.charAt(0);
+                                        if (q === '/' || q === '*') {
+                                            errorAt("Confusing regular expression.",
+                                                    line, from);
+                                        }
+                                        return it('(regexp)', c);
+                                    case '\\':
+                                        c = s.charAt(l);
+                                        if (c < ' ') {
+                                            warningAt(
+"Unexpected control character in regular expression.", line, from + l);
+                                        } else if (c === '<') {
+                                            warningAt(
+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                        }
+                                        l += 1;
+                                        break;
+                                    case '(':
+                                        depth += 1;
+                                        b = false;
+                                        if (s.charAt(l) === '?') {
+                                            l += 1;
+                                            switch (s.charAt(l)) {
+                                            case ':':
+                                            case '=':
+                                            case '!':
+                                                l += 1;
+                                                break;
+                                            default:
+                                                warningAt(
+"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
+                                            }
+                                        } else {
+                                            captures += 1;
+                                        }
+                                        break;
+                                    case '|':
+                                        b = false;
+                                        break;
+                                    case ')':
+                                        if (depth === 0) {
+                                            warningAt("Unescaped '{a}'.",
+                                                    line, from + l, ')');
+                                        } else {
+                                            depth -= 1;
+                                        }
+                                        break;
+                                    case ' ':
+                                        q = 1;
+                                        while (s.charAt(l) === ' ') {
+                                            l += 1;
+                                            q += 1;
+                                        }
+                                        if (q > 1) {
+                                            warningAt(
+"Spaces are hard to count. Use {{a}}.", line, from + l, q);
+                                        }
+                                        break;
+                                    case '[':
+                                        c = s.charAt(l);
+                                        if (c === '^') {
+                                            l += 1;
+                                            if (option.regexp) {
+                                                warningAt("Insecure '{a}'.",
+                                                        line, from + l, c);
+                                            } else if (s.charAt(l) === ']') {
+                                                errorAt("Unescaped '{a}'.",
+                                                    line, from + l, '^');
+                                            }
+                                        }
+                                        if (c === ']') {
+                                            warningAt("Empty class.", line,
+                                                    from + l - 1);
+                                        }
+                                        isLiteral = false;
+                                        isInRange = false;
+klass:                                  do {
+                                            c = s.charAt(l);
+                                            l += 1;
+                                            switch (c) {
+                                            case '[':
+                                            case '^':
+                                                warningAt("Unescaped '{a}'.",
+                                                        line, from + l, c);
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case '-':
+                                                if (isLiteral && !isInRange) {
+                                                    isLiteral = false;
+                                                    isInRange = true;
+                                                } else if (isInRange) {
+                                                    isInRange = false;
+                                                } else if (s.charAt(l) === ']') {
+                                                    isInRange = true;
+                                                } else {
+                                                    if (option.regexdash !== (l === 2 || (l === 3 &&
+                                                        s.charAt(1) === '^'))) {
+                                                        warningAt("Unescaped '{a}'.",
+                                                            line, from + l - 1, '-');
+                                                    }
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case ']':
+                                                if (isInRange && !option.regexdash) {
+                                                    warningAt("Unescaped '{a}'.",
+                                                            line, from + l - 1, '-');
+                                                }
+                                                break klass;
+                                            case '\\':
+                                                c = s.charAt(l);
+                                                if (c < ' ') {
+                                                    warningAt(
+"Unexpected control character in regular expression.", line, from + l);
+                                                } else if (c === '<') {
+                                                    warningAt(
+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                                }
+                                                l += 1;
+
+                                                // \w, \s and \d are never part of a character range
+                                                if (/[wsd]/i.test(c)) {
+                                                    if (isInRange) {
+                                                        warningAt("Unescaped '{a}'.",
+                                                            line, from + l, '-');
+                                                        isInRange = false;
+                                                    }
+                                                    isLiteral = false;
+                                                } else if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case '/':
+                                                warningAt("Unescaped '{a}'.",
+                                                        line, from + l - 1, '/');
+
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case '<':
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            default:
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                            }
+                                        } while (c);
+                                        break;
+                                    case '.':
+                                        if (option.regexp) {
+                                            warningAt("Insecure '{a}'.", line,
+                                                    from + l, c);
+                                        }
+                                        break;
+                                    case ']':
+                                    case '?':
+                                    case '{':
+                                    case '}':
+                                    case '+':
+                                    case '*':
+                                        warningAt("Unescaped '{a}'.", line,
+                                                from + l, c);
+                                    }
+                                    if (b) {
+                                        switch (s.charAt(l)) {
+                                        case '?':
+                                        case '+':
+                                        case '*':
+                                            l += 1;
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            break;
+                                        case '{':
+                                            l += 1;
+                                            c = s.charAt(l);
+                                            if (c < '0' || c > '9') {
+                                                warningAt(
+"Expected a number and instead saw '{a}'.", line, from + l, c);
+                                            }
+                                            l += 1;
+                                            low = +c;
+                                            for (;;) {
+                                                c = s.charAt(l);
+                                                if (c < '0' || c > '9') {
+                                                    break;
+                                                }
+                                                l += 1;
+                                                low = +c + (low * 10);
+                                            }
+                                            high = low;
+                                            if (c === ',') {
+                                                l += 1;
+                                                high = Infinity;
+                                                c = s.charAt(l);
+                                                if (c >= '0' && c <= '9') {
+                                                    l += 1;
+                                                    high = +c;
+                                                    for (;;) {
+                                                        c = s.charAt(l);
+                                                        if (c < '0' || c > '9') {
+                                                            break;
+                                                        }
+                                                        l += 1;
+                                                        high = +c + (high * 10);
+                                                    }
+                                                }
+                                            }
+                                            if (s.charAt(l) !== '}') {
+                                                warningAt(
+"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
+                                            } else {
+                                                l += 1;
+                                            }
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            if (low > high) {
+                                                warningAt(
+"'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+                                            }
+                                        }
+                                    }
+                                }
+                                c = s.substr(0, l - 1);
+                                character += l;
+                                s = s.substr(l);
+                                return it('(regexp)', c);
+                            }
+                            return it('(punctuator)', t);
+
+    //      punctuator
+
+                        case '#':
+                            return it('(punctuator)', t);
+                        default:
+                            return it('(punctuator)', t);
+                        }
+                    }
+                }
+            }
+        };
+    }());
+
+
+    function addlabel(t, type) {
+
+        if (t === 'hasOwnProperty') {
+            warning("'hasOwnProperty' is a really bad name.");
+        }
+
+// Define t in the current function in the current scope.
+        if (is_own(funct, t) && !funct['(global)']) {
+            if (funct[t] === true) {
+                if (option.latedef)
+                    warning("'{a}' was used before it was defined.", nexttoken, t);
+            } else {
+                if (!option.shadow && type !== "exception")
+                    warning("'{a}' is already defined.", nexttoken, t);
+            }
+        }
+
+        funct[t] = type;
+        if (funct['(global)']) {
+            global[t] = funct;
+            if (is_own(implied, t)) {
+                if (option.latedef)
+                    warning("'{a}' was used before it was defined.", nexttoken, t);
+                delete implied[t];
+            }
+        } else {
+            scope[t] = funct;
+        }
+    }
+
+
+    function doOption() {
+        var b, obj, filter, o = nexttoken.value, t, v;
+        switch (o) {
+        case '*/':
+            error("Unbegun comment.");
+            break;
+        case '/*members':
+        case '/*member':
+            o = '/*members';
+            if (!membersOnly) {
+                membersOnly = {};
+            }
+            obj = membersOnly;
+            break;
+        case '/*jshint':
+        case '/*jslint':
+            obj = option;
+            filter = boolOptions;
+            break;
+        case '/*global':
+            obj = predefined;
+            break;
+        default:
+            error("What?");
+        }
+        t = lex.token();
+loop:   for (;;) {
+            for (;;) {
+                if (t.type === 'special' && t.value === '*/') {
+                    break loop;
+                }
+                if (t.id !== '(endline)' && t.id !== ',') {
+                    break;
+                }
+                t = lex.token();
+            }
+            if (t.type !== '(string)' && t.type !== '(identifier)' &&
+                    o !== '/*members') {
+                error("Bad option.", t);
+            }
+            v = lex.token();
+            if (v.id === ':') {
+                v = lex.token();
+                if (obj === membersOnly) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, '*/', ':');
+                }
+                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.white = true;
+                    obj.indent = b;
+                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxerr = b;
+                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxlen = b;
+                } else if (t.value === 'validthis') {
+                    if (funct['(global)']) {
+                        error("Option 'validthis' can't be used in a global scope.");
+                    } else {
+                        if (v.value === 'true' || v.value === 'false')
+                            obj[t.value] = v.value === 'true';
+                        else
+                            error("Bad option value.", v);
+                    }
+                } else if (v.value === 'true') {
+                    obj[t.value] = true;
+                } else if (v.value === 'false') {
+                    obj[t.value] = false;
+                } else {
+                    error("Bad option value.", v);
+                }
+                t = lex.token();
+            } else {
+                if (o === '/*jshint' || o === '/*jslint') {
+                    error("Missing option value.", t);
+                }
+                obj[t.value] = false;
+                t = v;
+            }
+        }
+        if (filter) {
+            assume();
+        }
+    }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+//     for ( var i in ...
+// from
+//     for ( var i = ...
+
+    function peek(p) {
+        var i = p || 0, j = 0, t;
+
+        while (j <= i) {
+            t = lookahead[j];
+            if (!t) {
+                t = lookahead[j] = lex.token();
+            }
+            j += 1;
+        }
+        return t;
+    }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+    function advance(id, t) {
+        switch (token.id) {
+        case '(number)':
+            if (nexttoken.id === '.') {
+                warning("A dot following a number can be confused with a decimal point.", token);
+            }
+            break;
+        case '-':
+            if (nexttoken.id === '-' || nexttoken.id === '--') {
+                warning("Confusing minusses.");
+            }
+            break;
+        case '+':
+            if (nexttoken.id === '+' || nexttoken.id === '++') {
+                warning("Confusing plusses.");
+            }
+            break;
+        }
+
+        if (token.type === '(string)' || token.identifier) {
+            anonname = token.value;
+        }
+
+        if (id && nexttoken.id !== id) {
+            if (t) {
+                if (nexttoken.id === '(end)') {
+                    warning("Unmatched '{a}'.", t, t.id);
+                } else {
+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+                            nexttoken, id, t.id, t.line, nexttoken.value);
+                }
+            } else if (nexttoken.type !== '(identifier)' ||
+                            nexttoken.value !== id) {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, id, nexttoken.value);
+            }
+        }
+
+        prevtoken = token;
+        token = nexttoken;
+        for (;;) {
+            nexttoken = lookahead.shift() || lex.token();
+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                return;
+            }
+            if (nexttoken.type === 'special') {
+                doOption();
+            } else {
+                if (nexttoken.id !== '(endline)') {
+                    break;
+                }
+            }
+        }
+    }
+
+
+// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
+// like .nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define statement-oriented languages like
+// JavaScript. I retained Pratt's nomenclature.
+
+// .nud     Null denotation
+// .fud     First null denotation
+// .led     Left denotation
+//  lbp     Left binding power
+//  rbp     Right binding power
+
+// They are elements of the parsing method called Top Down Operator Precedence.
+
+    function expression(rbp, initial) {
+        var left, isArray = false;
+
+        if (nexttoken.id === '(end)')
+            error("Unexpected early end of program.", token);
+
+        advance();
+        if (initial) {
+            anonname = 'anonymous';
+            funct['(verb)'] = token.value;
+        }
+        if (initial === true && token.fud) {
+            left = token.fud();
+        } else {
+            if (token.nud) {
+                left = token.nud();
+            } else {
+                if (nexttoken.type === '(number)' && token.id === '.') {
+                    warning("A leading decimal point can be confused with a dot: '.{a}'.",
+                            token, nexttoken.value);
+                    advance();
+                    return token;
+                } else {
+                    error("Expected an identifier and instead saw '{a}'.",
+                            token, token.id);
+                }
+            }
+            while (rbp < nexttoken.lbp) {
+                isArray = token.value === 'Array';
+                advance();
+                if (isArray && token.id === '(' && nexttoken.id === ')')
+                    warning("Use the array literal notation [].", token);
+                if (token.led) {
+                    left = token.led(left);
+                } else {
+                    error("Expected an operator and instead saw '{a}'.",
+                        token, token.id);
+                }
+            }
+        }
+        return left;
+    }
+
+
+// Functions for conformance of style.
+
+    function adjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white) {
+            if (left.character !== right.from && left.line === right.line) {
+                left.from += (left.character - left.from);
+                warning("Unexpected space after '{a}'.", left, left.value);
+            }
+        }
+    }
+
+    function nobreak(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && (left.character !== right.from || left.line !== right.line)) {
+            warning("Unexpected space before '{a}'.", right, right.value);
+        }
+    }
+
+    function nospace(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && !left.comment) {
+            if (left.line === right.line) {
+                adjacent(left, right);
+            }
+        }
+    }
+
+    function nonadjacent(left, right) {
+        if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.line === right.line && left.character === right.from) {
+                left.from += (left.character - left.from);
+                warning("Missing space after '{a}'.",
+                        left, left.value);
+            }
+        }
+    }
+
+    function nobreaknonadjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (!option.laxbreak && left.line !== right.line) {
+            warning("Bad line breaking before '{a}'.", right, right.id);
+        } else if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.character === right.from) {
+                left.from += (left.character - left.from);
+                warning("Missing space after '{a}'.",
+                        left, left.value);
+            }
+        }
+    }
+
+    function indentation(bias) {
+        var i;
+        if (option.white && nexttoken.id !== '(end)') {
+            i = indent + (bias || 0);
+            if (nexttoken.from !== i) {
+                warning(
+"Expected '{a}' to have an indentation at {b} instead at {c}.",
+                        nexttoken, nexttoken.value, i, nexttoken.from);
+            }
+        }
+    }
+
+    function nolinebreak(t) {
+        t = t || token;
+        if (t.line !== nexttoken.line) {
+            warning("Line breaking error '{a}'.", t, t.value);
+        }
+    }
+
+
+    function comma() {
+        if (token.line !== nexttoken.line) {
+            if (!option.laxbreak) {
+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
+            }
+        } else if (!token.comment && token.character !== nexttoken.from && option.white) {
+            token.from += (token.character - token.from);
+            warning("Unexpected space after '{a}'.", token, token.value);
+        }
+        advance(',');
+        nonadjacent(token, nexttoken);
+    }
+
+
+// Functional constructors for making the symbols that will be inherited by
+// tokens.
+
+    function symbol(s, p) {
+        var x = syntax[s];
+        if (!x || typeof x !== 'object') {
+            syntax[s] = x = {
+                id: s,
+                lbp: p,
+                value: s
+            };
+        }
+        return x;
+    }
+
+
+    function delim(s) {
+        return symbol(s, 0);
+    }
+
+
+    function stmt(s, f) {
+        var x = delim(s);
+        x.identifier = x.reserved = true;
+        x.fud = f;
+        return x;
+    }
+
+
+    function blockstmt(s, f) {
+        var x = stmt(s, f);
+        x.block = true;
+        return x;
+    }
+
+
+    function reserveName(x) {
+        var c = x.id.charAt(0);
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+            x.identifier = x.reserved = true;
+        }
+        return x;
+    }
+
+
+    function prefix(s, f) {
+        var x = symbol(s, 150);
+        reserveName(x);
+        x.nud = (typeof f === 'function') ? f : function () {
+            this.right = expression(150);
+            this.arity = 'unary';
+            if (this.id === '++' || this.id === '--') {
+                if (option.plusplus) {
+                    warning("Unexpected use of '{a}'.", this, this.id);
+                } else if ((!this.right.identifier || this.right.reserved) &&
+                        this.right.id !== '.' && this.right.id !== '[') {
+                    warning("Bad operand.", this);
+                }
+            }
+            return this;
+        };
+        return x;
+    }
+
+
+    function type(s, f) {
+        var x = delim(s);
+        x.type = s;
+        x.nud = f;
+        return x;
+    }
+
+
+    function reserve(s, f) {
+        var x = type(s, f);
+        x.identifier = x.reserved = true;
+        return x;
+    }
+
+
+    function reservevar(s, v) {
+        return reserve(s, function () {
+            if (typeof v === 'function') {
+                v(this);
+            }
+            return this;
+        });
+    }
+
+
+    function infix(s, f, p, w) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = function (left) {
+            if (!w) {
+                nobreaknonadjacent(prevtoken, token);
+                nonadjacent(token, nexttoken);
+            }
+            if (s === "in" && left.id === "!") {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (typeof f === 'function') {
+                return f(left, this);
+            } else {
+                this.left = left;
+                this.right = expression(p);
+                return this;
+            }
+        };
+        return x;
+    }
+
+
+    function relation(s, f) {
+        var x = symbol(s, 100);
+        x.led = function (left) {
+            nobreaknonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            var right = expression(100);
+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
+                warning("Use the isNaN function to compare with NaN.", this);
+            } else if (f) {
+                f.apply(this, [left, right]);
+            }
+            if (left.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (right.id === '!') {
+                warning("Confusing use of '{a}'.", right, '!');
+            }
+            this.left = left;
+            this.right = right;
+            return this;
+        };
+        return x;
+    }
+
+
+    function isPoorRelation(node) {
+        return node &&
+              ((node.type === '(number)' && +node.value === 0) ||
+               (node.type === '(string)' && node.value === '') ||
+               (node.type === 'null' && !option.eqnull) ||
+                node.type === 'true' ||
+                node.type === 'false' ||
+                node.type === 'undefined');
+    }
+
+
+    function assignop(s, f) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            var l;
+            that.left = left;
+            if (predefined[left.value] === false &&
+                    scope[left.value]['(global)'] === true) {
+                warning("Read only.", left);
+            } else if (left['function']) {
+                warning("'{a}' is a function.", left, left.value);
+            }
+            if (left) {
+                if (option.esnext && funct[left.value] === 'const') {
+                    warning("Attempting to override '{a}' which is a constant", left, left.value);
+                }
+                if (left.id === '.' || left.id === '[') {
+                    if (!left.left || left.left.value === 'arguments') {
+                        warning('Bad assignment.', that);
+                    }
+                    that.right = expression(19);
+                    return that;
+                } else if (left.identifier && !left.reserved) {
+                    if (funct[left.value] === 'exception') {
+                        warning("Do not assign to the exception parameter.", left);
+                    }
+                    that.right = expression(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function bitwise(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.left = left;
+            this.right = expression(p);
+            return this;
+        };
+        return x;
+    }
+
+
+    function bitwiseassignop(s) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", that, that.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (left) {
+                if (left.id === '.' || left.id === '[' ||
+                        (left.identifier && !left.reserved)) {
+                    expression(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+                                token);
+                }
+                return that;
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function suffix(s, f) {
+        var x = symbol(s, 150);
+        x.led = function (left) {
+            if (option.plusplus) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            } else if ((!left.identifier || left.reserved) &&
+                    left.id !== '.' && left.id !== '[') {
+                warning("Bad operand.", this);
+            }
+            this.left = left;
+            return this;
+        };
+        return x;
+    }
+
+
+    // fnparam means that this identifier is being defined as a function
+    // argument (see identifier())
+    function optionalidentifier(fnparam) {
+        if (nexttoken.identifier) {
+            advance();
+            if (token.reserved && !option.es5) {
+                // `undefined` as a function param is a common pattern to protect
+                // against the case when somebody does `undefined = true` and
+                // help with minification. More info: https://gist.github.com/315916
+                if (!fnparam || token.value !== 'undefined') {
+                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+                            token, token.id);
+                }
+            }
+            return token.value;
+        }
+    }
+
+    // fnparam means that this identifier is being defined as a function
+    // argument
+    function identifier(fnparam) {
+        var i = optionalidentifier(fnparam);
+        if (i) {
+            return i;
+        }
+        if (token.id === 'function' && nexttoken.id === '(') {
+            warning("Missing name in function declaration.");
+        } else {
+            error("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    }
+
+
+    function reachable(s) {
+        var i = 0, t;
+        if (nexttoken.id !== ';' || noreach) {
+            return;
+        }
+        for (;;) {
+            t = peek(i);
+            if (t.reach) {
+                return;
+            }
+            if (t.id !== '(endline)') {
+                if (t.id === 'function') {
+                    warning(
+"Inner functions should be listed at the top of the outer function.", t);
+                    break;
+                }
+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+                break;
+            }
+            i += 1;
+        }
+    }
+
+
+    function statement(noindent) {
+        var i = indent, r, s = scope, t = nexttoken;
+
+        if (t.id === ";") {
+            advance(";");
+            return;
+        }
+
+// Is this a labelled statement?
+
+        if (t.identifier && !t.reserved && peek().id === ':') {
+            advance();
+            advance(':');
+            scope = Object.create(s);
+            addlabel(t.value, 'label');
+            if (!nexttoken.labelled) {
+                warning("Label '{a}' on {b} statement.",
+                        nexttoken, t.value, nexttoken.value);
+            }
+            if (jx.test(t.value + ':')) {
+                warning("Label '{a}' looks like a javascript url.",
+                        t, t.value);
+            }
+            nexttoken.label = t.value;
+            t = nexttoken;
+        }
+
+// Parse the statement.
+
+        if (!noindent) {
+            indentation();
+        }
+        r = expression(0, true);
+
+        // Look for the final semicolon.
+        if (!t.block) {
+            if (!option.expr && (!r || !r.exps)) {
+                warning("Expected an assignment or function call and instead saw an expression.",
+                    token);
+            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {
+                warning("Do not use 'new' for side effects.");
+            }
+
+            if (nexttoken.id !== ';') {
+                if (!option.asi) {
+                    // If this is the last statement in a block that ends on
+                    // the same line *and* option lastsemic is on, ignore the warning.
+                    // Otherwise, complain about missing semicolon.
+                    if (!option.lastsemic || nexttoken.id !== '}' ||
+                            nexttoken.line !== token.line) {
+                        warningAt("Missing semicolon.", token.line, token.character);
+                    }
+                }
+            } else {
+                adjacent(token, nexttoken);
+                advance(';');
+                nonadjacent(token, nexttoken);
+            }
+        }
+
+// Restore the indentation.
+
+        indent = i;
+        scope = s;
+        return r;
+    }
+
+
+    function statements(startLine) {
+        var a = [], f, p;
+
+        while (!nexttoken.reach && nexttoken.id !== '(end)') {
+            if (nexttoken.id === ';') {
+                warning("Unnecessary semicolon.");
+                advance(';');
+            } else {
+                a.push(statement(startLine === nexttoken.line));
+            }
+        }
+        return a;
+    }
+
+
+    /*
+     * read all directives
+     * recognizes a simple form of asi, but always
+     * warns, if it is used
+     */
+    function directives() {
+        var i, p, pn;
+
+        for (;;) {
+            if (nexttoken.id === "(string)") {
+                p = peek(0);
+                if (p.id === "(endline)") {
+                    i = 1;
+                    do {
+                        pn = peek(i);
+                        i = i + 1;
+                    } while (pn.id === "(endline)");
+
+                    if (pn.id !== ";") {
+                        if (pn.id !== "(string)" && pn.id !== "(number)" &&
+                            pn.id !== "(regexp)" && pn.identifier !== true &&
+                            pn.id !== "}") {
+                            break;
+                        }
+                        warning("Missing semicolon.", nexttoken);
+                    } else {
+                        p = pn;
+                    }
+                } else if (p.id === "}") {
+                    // directive with no other statements, warn about missing semicolon
+                    warning("Missing semicolon.", p);
+                } else if (p.id !== ";") {
+                    break;
+                }
+
+                indentation();
+                advance();
+                if (directive[token.value]) {
+                    warning("Unnecessary directive \"{a}\".", token, token.value);
+                }
+
+                if (token.value === "use strict") {
+                    option.newcap = true;
+                    option.undef = true;
+                }
+
+                // there's no directive negation, so always set to true
+                directive[token.value] = true;
+
+                if (p.id === ";") {
+                    advance(";");
+                }
+                continue;
+            }
+            break;
+        }
+    }
+
+
+    /*
+     * Parses a single block. A block is a sequence of statements wrapped in
+     * braces.
+     *
+     * ordinary - true for everything but function bodies and try blocks.
+     * stmt     - true if block can be a single statement (e.g. in if/for/while).
+     * isfunc   - true if block is a function body
+     */
+    function block(ordinary, stmt, isfunc) {
+        var a,
+            b = inblock,
+            old_indent = indent,
+            m,
+            s = scope,
+            t,
+            line,
+            d;
+
+        inblock = ordinary;
+        if (!ordinary || !option.funcscope) scope = Object.create(scope);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+
+        if (nexttoken.id === '{') {
+            advance('{');
+            line = token.line;
+            if (nexttoken.id !== '}') {
+                indent += option.indent;
+                while (!ordinary && nexttoken.from > indent) {
+                    indent += option.indent;
+                }
+
+                if (isfunc) {
+                    m = {};
+                    for (d in directive) {
+                        if (is_own(directive, d)) {
+                            m[d] = directive[d];
+                        }
+                    }
+                    directives();
+
+                    if (option.strict && funct['(context)']['(global)']) {
+                        if (!m["use strict"] && !directive["use strict"]) {
+                            warning("Missing \"use strict\" statement.");
+                        }
+                    }
+                }
+
+                a = statements(line);
+
+                if (isfunc) {
+                    directive = m;
+                }
+
+                indent -= option.indent;
+                if (line !== nexttoken.line) {
+                    indentation();
+                }
+            } else if (line !== nexttoken.line) {
+                indentation();
+            }
+            advance('}', t);
+            indent = old_indent;
+        } else if (!ordinary) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                  nexttoken, '{', nexttoken.value);
+        } else {
+            if (!stmt || option.curly)
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, '{', nexttoken.value);
+
+            noreach = true;
+            indent += option.indent;
+            // test indentation only if statement is in new line
+            a = [statement(nexttoken.line === token.line)];
+            indent -= option.indent;
+            noreach = false;
+        }
+        funct['(verb)'] = null;
+        if (!ordinary || !option.funcscope) scope = s;
+        inblock = b;
+        if (ordinary && option.noempty && (!a || a.length === 0)) {
+            warning("Empty block.");
+        }
+        return a;
+    }
+
+
+    function countMember(m) {
+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
+            warning("Unexpected /*member '{a}'.", token, m);
+        }
+        if (typeof member[m] === 'number') {
+            member[m] += 1;
+        } else {
+            member[m] = 1;
+        }
+    }
+
+
+    function note_implied(token) {
+        var name = token.value, line = token.line, a = implied[name];
+        if (typeof a === 'function') {
+            a = false;
+        }
+        if (!a) {
+            a = [line];
+            implied[name] = a;
+        } else if (a[a.length - 1] !== line) {
+            a.push(line);
+        }
+    }
+
+
+    // Build the syntax table by declaring the syntactic elements of the language.
+
+    type('(number)', function () {
+        return this;
+    });
+
+    type('(string)', function () {
+        return this;
+    });
+
+    syntax['(identifier)'] = {
+        type: '(identifier)',
+        lbp: 0,
+        identifier: true,
+        nud: function () {
+            var v = this.value,
+                s = scope[v],
+                f;
+
+            if (typeof s === 'function') {
+                // Protection against accidental inheritance.
+                s = undefined;
+            } else if (typeof s === 'boolean') {
+                f = funct;
+                funct = functions[0];
+                addlabel(v, 'var');
+                s = funct;
+                funct = f;
+            }
+
+            // The name is in scope and defined in the current function.
+            if (funct === s) {
+                // Change 'unused' to 'var', and reject labels.
+                switch (funct[v]) {
+                case 'unused':
+                    funct[v] = 'var';
+                    break;
+                case 'unction':
+                    funct[v] = 'function';
+                    this['function'] = true;
+                    break;
+                case 'function':
+                    this['function'] = true;
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                }
+            } else if (funct['(global)']) {
+                // The name is not defined in the function.  If we are in the global
+                // scope, then we have an undefined variable.
+                //
+                // Operators typeof and delete do not raise runtime errors even if
+                // the base object of a reference is null so no need to display warning
+                // if we're inside of typeof or delete.
+                if (anonname !== 'typeof' && anonname !== 'delete' &&
+                    option.undef && typeof predefined[v] !== 'boolean') {
+                    isundef(funct, "'{a}' is not defined.", token, v);
+                }
+                note_implied(token);
+            } else {
+                // If the name is already defined in the current
+                // function, but not as outer, then there is a scope error.
+
+                switch (funct[v]) {
+                case 'closure':
+                case 'function':
+                case 'var':
+                case 'unused':
+                    warning("'{a}' used out of scope.", token, v);
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                case 'outer':
+                case 'global':
+                    break;
+                default:
+                    // If the name is defined in an outer function, make an outer entry,
+                    // and if it was unused, make it var.
+                    if (s === true) {
+                        funct[v] = true;
+                    } else if (s === null) {
+                        warning("'{a}' is not allowed.", token, v);
+                        note_implied(token);
+                    } else if (typeof s !== 'object') {
+                        // Operators typeof and delete do not raise runtime errors even
+                        // if the base object of a reference is null so no need to
+                        // display warning if we're inside of typeof or delete.
+                        if (anonname !== 'typeof' && anonname !== 'delete' && option.undef) {
+                            isundef(funct, "'{a}' is not defined.", token, v);
+                        }
+                        funct[v] = true;
+                        note_implied(token);
+                    } else {
+                        switch (s[v]) {
+                        case 'function':
+                        case 'unction':
+                            this['function'] = true;
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'var':
+                        case 'unused':
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'closure':
+                        case 'parameter':
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'label':
+                            warning("'{a}' is a statement label.", token, v);
+                        }
+                    }
+                }
+            }
+            return this;
+        },
+        led: function () {
+            error("Expected an operator and instead saw '{a}'.",
+                nexttoken, nexttoken.value);
+        }
+    };
+
+    type('(regexp)', function () {
+        return this;
+    });
+
+
+// ECMAScript parser
+
+    delim('(endline)');
+    delim('(begin)');
+    delim('(end)').reach = true;
+    delim('</').reach = true;
+    delim('<!');
+    delim('<!--');
+    delim('-->');
+    delim('(error)').reach = true;
+    delim('}').reach = true;
+    delim(')');
+    delim(']');
+    delim('"').reach = true;
+    delim("'").reach = true;
+    delim(';');
+    delim(':').reach = true;
+    delim(',');
+    delim('#');
+    delim('@');
+    reserve('else');
+    reserve('case').reach = true;
+    reserve('catch');
+    reserve('default').reach = true;
+    reserve('finally');
+    reservevar('arguments', function (x) {
+        if (directive['use strict'] && funct['(global)']) {
+            warning("Strict violation.", x);
+        }
+    });
+    reservevar('eval');
+    reservevar('false');
+    reservevar('Infinity');
+    reservevar('NaN');
+    reservevar('null');
+    reservevar('this', function (x) {
+        if (directive['use strict'] && !option.validthis && ((funct['(statement)'] &&
+                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {
+            warning("Possible strict violation.", x);
+        }
+    });
+    reservevar('true');
+    reservevar('undefined');
+    assignop('=', 'assign', 20);
+    assignop('+=', 'assignadd', 20);
+    assignop('-=', 'assignsub', 20);
+    assignop('*=', 'assignmult', 20);
+    assignop('/=', 'assigndiv', 20).nud = function () {
+        error("A regular expression literal can be confused with '/='.");
+    };
+    assignop('%=', 'assignmod', 20);
+    bitwiseassignop('&=', 'assignbitand', 20);
+    bitwiseassignop('|=', 'assignbitor', 20);
+    bitwiseassignop('^=', 'assignbitxor', 20);
+    bitwiseassignop('<<=', 'assignshiftleft', 20);
+    bitwiseassignop('>>=', 'assignshiftright', 20);
+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
+    infix('?', function (left, that) {
+        that.left = left;
+        that.right = expression(10);
+        advance(':');
+        that['else'] = expression(10);
+        return that;
+    }, 30);
+
+    infix('||', 'or', 40);
+    infix('&&', 'and', 50);
+    bitwise('|', 'bitor', 70);
+    bitwise('^', 'bitxor', 80);
+    bitwise('&', 'bitand', 90);
+    relation('==', function (left, right) {
+        var eqnull = option.eqnull && (left.value === 'null' || right.value === 'null');
+
+        if (!eqnull && option.eqeqeq)
+            warning("Expected '{a}' and instead saw '{b}'.", this, '===', '==');
+        else if (isPoorRelation(left))
+            warning("Use '{a}' to compare with '{b}'.", this, '===', left.value);
+        else if (isPoorRelation(right))
+            warning("Use '{a}' to compare with '{b}'.", this, '===', right.value);
+
+        return this;
+    });
+    relation('===');
+    relation('!=', function (left, right) {
+        var eqnull = option.eqnull &&
+                (left.value === 'null' || right.value === 'null');
+
+        if (!eqnull && option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '!==', '!=');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', right.value);
+        }
+        return this;
+    });
+    relation('!==');
+    relation('<');
+    relation('>');
+    relation('<=');
+    relation('>=');
+    bitwise('<<', 'shiftleft', 120);
+    bitwise('>>', 'shiftright', 120);
+    bitwise('>>>', 'shiftrightunsigned', 120);
+    infix('in', 'in', 120);
+    infix('instanceof', 'instanceof', 120);
+    infix('+', function (left, that) {
+        var right = expression(130);
+        if (left && right && left.id === '(string)' && right.id === '(string)') {
+            left.value += right.value;
+            left.character = right.character;
+            if (!option.scripturl && jx.test(left.value)) {
+                warning("JavaScript URL.", left);
+            }
+            return left;
+        }
+        that.left = left;
+        that.right = right;
+        return that;
+    }, 130);
+    prefix('+', 'num');
+    prefix('+++', function () {
+        warning("Confusing pluses.");
+        this.right = expression(150);
+        this.arity = 'unary';
+        return this;
+    });
+    infix('+++', function (left) {
+        warning("Confusing pluses.");
+        this.left = left;
+        this.right = expression(130);
+        return this;
+    }, 130);
+    infix('-', 'sub', 130);
+    prefix('-', 'neg');
+    prefix('---', function () {
+        warning("Confusing minuses.");
+        this.right = expression(150);
+        this.arity = 'unary';
+        return this;
+    });
+    infix('---', function (left) {
+        warning("Confusing minuses.");
+        this.left = left;
+        this.right = expression(130);
+        return this;
+    }, 130);
+    infix('*', 'mult', 140);
+    infix('/', 'div', 140);
+    infix('%', 'mod', 140);
+
+    suffix('++', 'postinc');
+    prefix('++', 'preinc');
+    syntax['++'].exps = true;
+
+    suffix('--', 'postdec');
+    prefix('--', 'predec');
+    syntax['--'].exps = true;
+    prefix('delete', function () {
+        var p = expression(0);
+        if (!p || (p.id !== '.' && p.id !== '[')) {
+            warning("Variables should not be deleted.");
+        }
+        this.first = p;
+        return this;
+    }).exps = true;
+
+    prefix('~', function () {
+        if (option.bitwise) {
+            warning("Unexpected '{a}'.", this, '~');
+        }
+        expression(150);
+        return this;
+    });
+
+    prefix('!', function () {
+        this.right = expression(150);
+        this.arity = 'unary';
+        if (bang[this.right.id] === true) {
+            warning("Confusing use of '{a}'.", this, '!');
+        }
+        return this;
+    });
+    prefix('typeof', 'typeof');
+    prefix('new', function () {
+        var c = expression(155), i;
+        if (c && c.id !== 'function') {
+            if (c.identifier) {
+                c['new'] = true;
+                switch (c.value) {
+                case 'Object':
+                    warning("Use the object literal notation {}.", token);
+                    break;
+                case 'Number':
+                case 'String':
+                case 'Boolean':
+                case 'Math':
+                case 'JSON':
+                    warning("Do not use {a} as a constructor.", token, c.value);
+                    break;
+                case 'Function':
+                    if (!option.evil) {
+                        warning("The Function constructor is eval.");
+                    }
+                    break;
+                case 'Date':
+                case 'RegExp':
+                    break;
+                default:
+                    if (c.id !== 'function') {
+                        i = c.value.substr(0, 1);
+                        if (option.newcap && (i < 'A' || i > 'Z')) {
+                            warning("A constructor name should start with an uppercase letter.",
+                                token);
+                        }
+                    }
+                }
+            } else {
+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
+                    warning("Bad constructor.", token);
+                }
+            }
+        } else {
+            if (!option.supernew)
+                warning("Weird construction. Delete 'new'.", this);
+        }
+        adjacent(token, nexttoken);
+        if (nexttoken.id !== '(' && !option.supernew) {
+            warning("Missing '()' invoking a constructor.");
+        }
+        this.first = c;
+        return this;
+    });
+    syntax['new'].exps = true;
+
+    prefix('void').exps = true;
+
+    infix('.', function (left, that) {
+        adjacent(prevtoken, token);
+        nobreak();
+        var m = identifier();
+        if (typeof m === 'string') {
+            countMember(m);
+        }
+        that.left = left;
+        that.right = m;
+        if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) {
+            if (option.noarg)
+                warning("Avoid arguments.{a}.", left, m);
+            else if (directive['use strict'])
+                error('Strict violation.');
+        } else if (!option.evil && left && left.value === 'document' &&
+                (m === 'write' || m === 'writeln')) {
+            warning("document.write can be a form of eval.", left);
+        }
+        if (!option.evil && (m === 'eval' || m === 'execScript')) {
+            warning('eval is evil.');
+        }
+        return that;
+    }, 160, true);
+
+    infix('(', function (left, that) {
+        if (prevtoken.id !== '}' && prevtoken.id !== ')') {
+            nobreak(prevtoken, token);
+        }
+        nospace();
+        if (option.immed && !left.immed && left.id === 'function') {
+            warning("Wrap an immediate function invocation in parentheses " +
+                "to assist the reader in understanding that the expression " +
+                "is the result of a function, and not the function itself.");
+        }
+        var n = 0,
+            p = [];
+        if (left) {
+            if (left.type === '(identifier)') {
+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+                    if (left.value !== 'Number' && left.value !== 'String' &&
+                            left.value !== 'Boolean' &&
+                            left.value !== 'Date') {
+                        if (left.value === 'Math') {
+                            warning("Math is not a function.", left);
+                        } else if (option.newcap) {
+                            warning(
+"Missing 'new' prefix when invoking a constructor.", left);
+                        }
+                    }
+                }
+            }
+        }
+        if (nexttoken.id !== ')') {
+            for (;;) {
+                p[p.length] = expression(10);
+                n += 1;
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+        }
+        advance(')');
+        nospace(prevtoken, token);
+        if (typeof left === 'object') {
+            if (left.value === 'parseInt' && n === 1) {
+                warning("Missing radix parameter.", left);
+            }
+            if (!option.evil) {
+                if (left.value === 'eval' || left.value === 'Function' ||
+                        left.value === 'execScript') {
+                    warning("eval is evil.", left);
+                } else if (p[0] && p[0].id === '(string)' &&
+                       (left.value === 'setTimeout' ||
+                        left.value === 'setInterval')) {
+                    warning(
+    "Implied eval is evil. Pass a function instead of a string.", left);
+                }
+            }
+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
+                    left.id !== '?') {
+                warning("Bad invocation.", left);
+            }
+        }
+        that.left = left;
+        return that;
+    }, 155, true).exps = true;
+
+    prefix('(', function () {
+        nospace();
+        if (nexttoken.id === 'function') {
+            nexttoken.immed = true;
+        }
+        var v = expression(0);
+        advance(')', this);
+        nospace(prevtoken, token);
+        if (option.immed && v.id === 'function') {
+            if (nexttoken.id === '(') {
+                warning(
+"Move the invocation into the parens that contain the function.", nexttoken);
+            } else {
+                warning(
+"Do not wrap function literals in parens unless they are to be immediately invoked.",
+                        this);
+            }
+        }
+        return v;
+    });
+
+    infix('[', function (left, that) {
+        nobreak(prevtoken, token);
+        nospace();
+        var e = expression(0), s;
+        if (e && e.type === '(string)') {
+            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {
+                warning("eval is evil.", that);
+            }
+            countMember(e.value);
+            if (!option.sub && ix.test(e.value)) {
+                s = syntax[e.value];
+                if (!s || !s.reserved) {
+                    warning("['{a}'] is better written in dot notation.",
+                            e, e.value);
+                }
+            }
+        }
+        advance(']', that);
+        nospace(prevtoken, token);
+        that.left = left;
+        that.right = e;
+        return that;
+    }, 160, true);
+
+    prefix('[', function () {
+        var b = token.line !== nexttoken.line;
+        this.first = [];
+        if (b) {
+            indent += option.indent;
+            if (nexttoken.from === indent + option.indent) {
+                indent += option.indent;
+            }
+        }
+        while (nexttoken.id !== '(end)') {
+            while (nexttoken.id === ',') {
+                warning("Extra comma.");
+                advance(',');
+            }
+            if (nexttoken.id === ']') {
+                break;
+            }
+            if (b && token.line !== nexttoken.line) {
+                indentation();
+            }
+            this.first.push(expression(10));
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.id === ']' && !option.es5) {
+                    warning("Extra comma.", token);
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        if (b) {
+            indent -= option.indent;
+            indentation();
+        }
+        advance(']', this);
+        return this;
+    }, 160);
+
+
+    function property_name() {
+        var id = optionalidentifier(true);
+        if (!id) {
+            if (nexttoken.id === '(string)') {
+                id = nexttoken.value;
+                advance();
+            } else if (nexttoken.id === '(number)') {
+                id = nexttoken.value.toString();
+                advance();
+            }
+        }
+        return id;
+    }
+
+
+    function functionparams() {
+        var i, t = nexttoken, p = [];
+        advance('(');
+        nospace();
+        if (nexttoken.id === ')') {
+            advance(')');
+            return;
+        }
+        for (;;) {
+            i = identifier(true);
+            p.push(i);
+            addlabel(i, 'parameter');
+            if (nexttoken.id === ',') {
+                comma();
+            } else {
+                advance(')', t);
+                nospace(prevtoken, token);
+                return p;
+            }
+        }
+    }
+
+
+    function doFunction(i, statement) {
+        var f,
+            oldOption = option,
+            oldScope  = scope;
+
+        option = Object.create(option);
+        scope = Object.create(scope);
+
+        funct = {
+            '(name)'     : i || '"' + anonname + '"',
+            '(line)'     : nexttoken.line,
+            '(context)'  : funct,
+            '(breakage)' : 0,
+            '(loopage)'  : 0,
+            '(scope)'    : scope,
+            '(statement)': statement
+        };
+        f = funct;
+        token.funct = funct;
+        functions.push(funct);
+        if (i) {
+            addlabel(i, 'function');
+        }
+        funct['(params)'] = functionparams();
+
+        block(false, false, true);
+        scope = oldScope;
+        option = oldOption;
+        funct['(last)'] = token.line;
+        funct = funct['(context)'];
+        return f;
+    }
+
+
+    (function (x) {
+        x.nud = function () {
+            var b, f, i, j, p, seen = {}, t;
+            var prop, acc = {}; // Accessor methods
+
+            function saveSetter(name, token) {
+                if (!acc[name]) {
+                    acc[name] = {};
+                }
+                acc[name].setter = true;
+                acc[name].setterToken = token;
+            }
+
+            function saveGetter(name) {
+                if (!acc[name]) {
+                    acc[name] = {};
+                }
+                acc[name].getter = true;
+            }
+
+            b = token.line !== nexttoken.line;
+            if (b) {
+                indent += option.indent;
+                if (nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+            }
+            for (;;) {
+                if (nexttoken.id === '}') {
+                    break;
+                }
+                if (b) {
+                    indentation();
+                }
+                if (nexttoken.value === 'get' && peek().id !== ':') {
+                    advance('get');
+                    if (!option.es5) {
+                        error("get/set are ES5 features.");
+                    }
+                    i = property_name();
+                    if (!i) {
+                        error("Missing property name.");
+                    }
+                    saveGetter(i);
+                    t = nexttoken;
+                    adjacent(token, nexttoken);
+                    f = doFunction();
+                    if (!option.loopfunc && funct['(loopage)']) {
+                        warning("Don't make functions within a loop.", t);
+                    }
+                    p = f['(params)'];
+                    if (p) {
+                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);
+                    }
+                    adjacent(token, nexttoken);
+                } else if (nexttoken.value === 'set' && peek().id !== ':') {
+                    advance('set');
+                    if (!option.es5) {
+                        error("get/set are ES5 features.");
+                    }
+                    i = property_name();
+                    if (!i) {
+                        error("Missing property name.");
+                    }
+                    if (acc[i] && acc[i].setter) {
+                        warning("Duplicate member '{a}'.", nexttoken, i);
+                    }
+                    saveSetter(i, nexttoken);
+                    seen[i] = false;
+                    t = nexttoken;
+                    adjacent(token, nexttoken);
+                    f = doFunction();
+                    p = f['(params)'];
+                    if (!p || p.length !== 1 || p[0] !== 'value') {
+                        warning("Expected (value) in set {a} function.", t, i);
+                    }
+                } else {
+                    i = property_name();
+                    if (typeof i !== 'string') {
+                        break;
+                    }
+                    advance(':');
+                    nonadjacent(token, nexttoken);
+                    expression(10);
+                }
+                if (seen[i] === true) {
+                    warning("Duplicate member '{a}'.", nexttoken, i);
+                }
+                seen[i] = true;
+                countMember(i);
+                if (nexttoken.id === ',') {
+                    comma();
+                    if (nexttoken.id === ',') {
+                        warning("Extra comma.", token);
+                    } else if (nexttoken.id === '}' && !option.es5) {
+                        warning("Extra comma.", token);
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (b) {
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', this);
+
+            // Check for lonely setters if in the ES5 mode.
+            if (option.es5) {
+                for (prop in acc) {
+                    if (acc.hasOwnProperty(prop) && acc[prop].setter && !acc[prop].getter) {
+                        warning("Setter is defined without getter.", acc[prop].setterToken);
+                    }
+                }
+            }
+            return this;
+        };
+        x.fud = function () {
+            error("Expected to see a statement and instead saw a block.", token);
+        };
+    }(delim('{')));
+
+// This Function is called when esnext option is set to true
+// it adds the `const` statement to JSHINT
+
+    useESNextSyntax = function () {
+        var conststatement = stmt('const', function (prefix) {
+            var id, name, value;
+
+            this.first = [];
+            for (;;) {
+                nonadjacent(token, nexttoken);
+                id = identifier();
+                if (funct[id] === "const") {
+                    warning("const '" + id + "' has already been declared");
+                }
+                if (funct['(global)'] && predefined[id] === false) {
+                    warning("Redefinition of '{a}'.", token, id);
+                }
+                addlabel(id, 'const');
+                if (prefix) {
+                    break;
+                }
+                name = token;
+                this.first.push(token);
+
+                if (nexttoken.id !== "=") {
+                    warning("const " +
+                      "'{a}' is initialized to 'undefined'.", token, id);
+                }
+
+                if (nexttoken.id === '=') {
+                    nonadjacent(token, nexttoken);
+                    advance('=');
+                    nonadjacent(token, nexttoken);
+                    if (nexttoken.id === 'undefined') {
+                        warning("It is not necessary to initialize " +
+                          "'{a}' to 'undefined'.", token, id);
+                    }
+                    if (peek(0).id === '=' && nexttoken.identifier) {
+                        error("Constant {a} was not declared correctly.",
+                                nexttoken, nexttoken.value);
+                    }
+                    value = expression(0);
+                    name.first = value;
+                }
+
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+            return this;
+        });
+        conststatement.exps = true;
+    };
+
+    var varstatement = stmt('var', function (prefix) {
+        // JavaScript does not have block scope. It only has function scope. So,
+        // declaring a variable in a block can have unexpected consequences.
+        var id, name, value;
+
+        if (funct['(onevar)'] && option.onevar) {
+            warning("Too many var statements.");
+        } else if (!funct['(global)']) {
+            funct['(onevar)'] = true;
+        }
+        this.first = [];
+        for (;;) {
+            nonadjacent(token, nexttoken);
+            id = identifier();
+            if (option.esnext && funct[id] === "const") {
+                warning("const '" + id + "' has already been declared");
+            }
+            if (funct['(global)'] && predefined[id] === false) {
+                warning("Redefinition of '{a}'.", token, id);
+            }
+            addlabel(id, 'unused');
+            if (prefix) {
+                break;
+            }
+            name = token;
+            this.first.push(token);
+            if (nexttoken.id === '=') {
+                nonadjacent(token, nexttoken);
+                advance('=');
+                nonadjacent(token, nexttoken);
+                if (nexttoken.id === 'undefined') {
+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
+                }
+                if (peek(0).id === '=' && nexttoken.identifier) {
+                    error("Variable {a} was not declared correctly.",
+                            nexttoken, nexttoken.value);
+                }
+                value = expression(0);
+                name.first = value;
+            }
+            if (nexttoken.id !== ',') {
+                break;
+            }
+            comma();
+        }
+        return this;
+    });
+    varstatement.exps = true;
+
+    blockstmt('function', function () {
+        if (inblock) {
+            warning("Function declarations should not be placed in blocks. " +
+                "Use a function expression or move the statement to the top of " +
+                "the outer function.", token);
+
+        }
+        var i = identifier();
+        if (option.esnext && funct[i] === "const") {
+            warning("const '" + i + "' has already been declared");
+        }
+        adjacent(token, nexttoken);
+        addlabel(i, 'unction');
+        doFunction(i, true);
+        if (nexttoken.id === '(' && nexttoken.line === token.line) {
+            error(
+"Function declarations are not invocable. Wrap the whole function invocation in parens.");
+        }
+        return this;
+    });
+
+    prefix('function', function () {
+        var i = optionalidentifier();
+        if (i) {
+            adjacent(token, nexttoken);
+        } else {
+            nonadjacent(token, nexttoken);
+        }
+        doFunction(i);
+        if (!option.loopfunc && funct['(loopage)']) {
+            warning("Don't make functions within a loop.");
+        }
+        return this;
+    });
+
+    blockstmt('if', function () {
+        var t = nexttoken;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        expression(20);
+        if (nexttoken.id === '=') {
+            if (!option.boss)
+                warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            expression(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true, true);
+        if (nexttoken.id === 'else') {
+            nonadjacent(token, nexttoken);
+            advance('else');
+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
+                statement(true);
+            } else {
+                block(true, true);
+            }
+        }
+        return this;
+    });
+
+    blockstmt('try', function () {
+        var b, e, s;
+
+        block(false);
+        if (nexttoken.id === 'catch') {
+            advance('catch');
+            nonadjacent(token, nexttoken);
+            advance('(');
+            s = scope;
+            scope = Object.create(s);
+            e = nexttoken.value;
+            if (nexttoken.type !== '(identifier)') {
+                warning("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, e);
+            } else {
+                addlabel(e, 'exception');
+            }
+            advance();
+            advance(')');
+            block(false);
+            b = true;
+            scope = s;
+        }
+        if (nexttoken.id === 'finally') {
+            advance('finally');
+            block(false);
+            return;
+        } else if (!b) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, 'catch', nexttoken.value);
+        }
+        return this;
+    });
+
+    blockstmt('while', function () {
+        var t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        expression(20);
+        if (nexttoken.id === '=') {
+            if (!option.boss)
+                warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            expression(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true, true);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+        return this;
+    }).labelled = true;
+
+    reserve('with');
+
+    blockstmt('switch', function () {
+        var t = nexttoken,
+            g = false;
+        funct['(breakage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        this.condition = expression(20);
+        advance(')', t);
+        nospace(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        advance('{');
+        nonadjacent(token, nexttoken);
+        indent += option.indent;
+        this.cases = [];
+        for (;;) {
+            switch (nexttoken.id) {
+            case 'case':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'case':
+                case 'continue':
+                case 'return':
+                case 'switch':
+                case 'throw':
+                    break;
+                default:
+                    // You can tell JSHint that you don't use break intentionally by
+                    // adding a comment /* falls through */ on a line just before
+                    // the next `case`.
+                    if (!ft.test(lines[nexttoken.line - 2])) {
+                        warning(
+                            "Expected a 'break' statement before 'case'.",
+                            token);
+                    }
+                }
+                indentation(-option.indent);
+                advance('case');
+                this.cases.push(expression(20));
+                g = true;
+                advance(':');
+                funct['(verb)'] = 'case';
+                break;
+            case 'default':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'continue':
+                case 'return':
+                case 'throw':
+                    break;
+                default:
+                    if (!ft.test(lines[nexttoken.line - 2])) {
+                        warning(
+                            "Expected a 'break' statement before 'default'.",
+                            token);
+                    }
+                }
+                indentation(-option.indent);
+                advance('default');
+                g = true;
+                advance(':');
+                break;
+            case '}':
+                indent -= option.indent;
+                indentation();
+                advance('}', t);
+                if (this.cases.length === 1 || this.condition.id === 'true' ||
+                        this.condition.id === 'false') {
+                    if (!option.onecase)
+                        warning("This 'switch' should be an 'if'.", this);
+                }
+                funct['(breakage)'] -= 1;
+                funct['(verb)'] = undefined;
+                return;
+            case '(end)':
+                error("Missing '{a}'.", nexttoken, '}');
+                return;
+            default:
+                if (g) {
+                    switch (token.id) {
+                    case ',':
+                        error("Each value should have its own case label.");
+                        return;
+                    case ':':
+                        g = false;
+                        statements();
+                        break;
+                    default:
+                        error("Missing ':' on a case clause.", token);
+                        return;
+                    }
+                } else {
+                    if (token.id === ':') {
+                        advance(':');
+                        error("Unexpected '{a}'.", token, ':');
+                        statements();
+                    } else {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, 'case', nexttoken.value);
+                        return;
+                    }
+                }
+            }
+        }
+    }).labelled = true;
+
+    stmt('debugger', function () {
+        if (!option.debug) {
+            warning("All 'debugger' statements should be removed.");
+        }
+        return this;
+    }).exps = true;
+
+    (function () {
+        var x = stmt('do', function () {
+            funct['(breakage)'] += 1;
+            funct['(loopage)'] += 1;
+            this.first = block(true);
+            advance('while');
+            var t = nexttoken;
+            nonadjacent(token, t);
+            advance('(');
+            nospace();
+            expression(20);
+            if (nexttoken.id === '=') {
+                if (!option.boss)
+                    warning("Expected a conditional expression and instead saw an assignment.");
+                advance('=');
+                expression(20);
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        });
+        x.labelled = true;
+        x.exps = true;
+    }());
+
+    blockstmt('for', function () {
+        var s, t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
+            if (nexttoken.id === 'var') {
+                advance('var');
+                varstatement.fud.call(varstatement, true);
+            } else {
+                switch (funct[nexttoken.value]) {
+                case 'unused':
+                    funct[nexttoken.value] = 'var';
+                    break;
+                case 'var':
+                    break;
+                default:
+                    warning("Bad for in variable '{a}'.",
+                            nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance('in');
+            expression(20);
+            advance(')', t);
+            s = block(true, true);
+            if (option.forin && s && (s.length > 1 || typeof s[0] !== 'object' ||
+                    s[0].value !== 'if')) {
+                warning("The body of a for in should be wrapped in an if statement to filter " +
+                        "unwanted properties from the prototype.", this);
+            }
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        } else {
+            if (nexttoken.id !== ';') {
+                if (nexttoken.id === 'var') {
+                    advance('var');
+                    varstatement.fud.call(varstatement);
+                } else {
+                    for (;;) {
+                        expression(0, 'for');
+                        if (nexttoken.id !== ',') {
+                            break;
+                        }
+                        comma();
+                    }
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id !== ';') {
+                expression(20);
+                if (nexttoken.id === '=') {
+                    if (!option.boss)
+                        warning("Expected a conditional expression and instead saw an assignment.");
+                    advance('=');
+                    expression(20);
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id === ';') {
+                error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, ')', ';');
+            }
+            if (nexttoken.id !== ')') {
+                for (;;) {
+                    expression(0, 'for');
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    comma();
+                }
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            block(true, true);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        }
+    }).labelled = true;
+
+
+    stmt('break', function () {
+        var v = nexttoken.value;
+
+        if (funct['(breakage)'] === 0)
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+
+        if (!option.asi)
+            nolinebreak(this);
+
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        }
+        reachable('break');
+        return this;
+    }).exps = true;
+
+
+    stmt('continue', function () {
+        var v = nexttoken.value;
+
+        if (funct['(breakage)'] === 0)
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+
+        if (!option.asi)
+            nolinebreak(this);
+
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        } else if (!funct['(loopage)']) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        reachable('continue');
+        return this;
+    }).exps = true;
+
+
+    stmt('return', function () {
+        if (this.line === nexttoken.line) {
+            if (nexttoken.id === '(regexp)')
+                warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+
+            if (nexttoken.id !== ';' && !nexttoken.reach) {
+                nonadjacent(token, nexttoken);
+                if (peek().value === "=" && !option.boss) {
+                    warningAt("Did you mean to return a conditional instead of an assignment?",
+                              token.line, token.character + 1);
+                }
+                this.first = expression(0);
+            }
+        } else if (!option.asi) {
+            nolinebreak(this); // always warn (Line breaking error)
+        }
+        reachable('return');
+        return this;
+    }).exps = true;
+
+
+    stmt('throw', function () {
+        nolinebreak(this);
+        nonadjacent(token, nexttoken);
+        this.first = expression(20);
+        reachable('throw');
+        return this;
+    }).exps = true;
+
+//  Superfluous reserved words
+
+    reserve('class');
+    reserve('const');
+    reserve('enum');
+    reserve('export');
+    reserve('extends');
+    reserve('import');
+    reserve('super');
+
+    reserve('let');
+    reserve('yield');
+    reserve('implements');
+    reserve('interface');
+    reserve('package');
+    reserve('private');
+    reserve('protected');
+    reserve('public');
+    reserve('static');
+
+
+// Parse JSON
+
+    function jsonValue() {
+
+        function jsonObject() {
+            var o = {}, t = nexttoken;
+            advance('{');
+            if (nexttoken.id !== '}') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '}' to match '{' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === '}') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    } else if (nexttoken.id !== '(string)') {
+                        warning("Expected a string and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                    }
+                    if (o[nexttoken.value] === true) {
+                        warning("Duplicate key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else if ((nexttoken.value === '__proto__' &&
+                        !option.proto) || (nexttoken.value === '__iterator__' &&
+                        !option.iterator)) {
+                        warning("The '{a}' key may produce unexpected results.",
+                            nexttoken, nexttoken.value);
+                    } else {
+                        o[nexttoken.value] = true;
+                    }
+                    advance();
+                    advance(':');
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance('}');
+        }
+
+        function jsonArray() {
+            var t = nexttoken;
+            advance('[');
+            if (nexttoken.id !== ']') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing ']' to match '[' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === ']') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    }
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(']');
+        }
+
+        switch (nexttoken.id) {
+        case '{':
+            jsonObject();
+            break;
+        case '[':
+            jsonArray();
+            break;
+        case 'true':
+        case 'false':
+        case 'null':
+        case '(number)':
+        case '(string)':
+            advance();
+            break;
+        case '-':
+            advance('-');
+            if (token.character !== nexttoken.from) {
+                warning("Unexpected space after '-'.", token);
+            }
+            adjacent(token, nexttoken);
+            advance('(number)');
+            break;
+        default:
+            error("Expected a JSON value.", nexttoken);
+        }
+    }
+
+
+// The actual JSHINT function itself.
+
+    var itself = function (s, o, g) {
+        var a, i, k;
+        JSHINT.errors = [];
+        JSHINT.undefs = [];
+        predefined = Object.create(standard);
+        combine(predefined, g || {});
+        if (o) {
+            a = o.predef;
+            if (a) {
+                if (Array.isArray(a)) {
+                    for (i = 0; i < a.length; i += 1) {
+                        predefined[a[i]] = true;
+                    }
+                } else if (typeof a === 'object') {
+                    k = Object.keys(a);
+                    for (i = 0; i < k.length; i += 1) {
+                        predefined[k[i]] = !!a[k[i]];
+                    }
+                }
+            }
+            option = o;
+        } else {
+            option = {};
+        }
+        option.indent = option.indent || 4;
+        option.maxerr = option.maxerr || 50;
+
+        tab = '';
+        for (i = 0; i < option.indent; i += 1) {
+            tab += ' ';
+        }
+        indent = 1;
+        global = Object.create(predefined);
+        scope = global;
+        funct = {
+            '(global)': true,
+            '(name)': '(global)',
+            '(scope)': scope,
+            '(breakage)': 0,
+            '(loopage)': 0
+        };
+        functions = [funct];
+        urls = [];
+        stack = null;
+        member = {};
+        membersOnly = null;
+        implied = {};
+        inblock = false;
+        lookahead = [];
+        jsonmode = false;
+        warnings = 0;
+        lex.init(s);
+        prereg = true;
+        directive = {};
+
+        prevtoken = token = nexttoken = syntax['(begin)'];
+        assume();
+
+        // combine the passed globals after we've assumed all our options
+        combine(predefined, g || {});
+
+        try {
+            advance();
+            switch (nexttoken.id) {
+            case '{':
+            case '[':
+                option.laxbreak = true;
+                jsonmode = true;
+                jsonValue();
+                break;
+            default:
+                directives();
+                if (directive["use strict"] && !option.globalstrict) {
+                    warning("Use the function form of \"use strict\".", prevtoken);
+                }
+
+                statements();
+            }
+            advance('(end)');
+        } catch (e) {
+            if (e) {
+                var nt = nexttoken || {};
+                JSHINT.errors.push({
+                    raw       : e.raw,
+                    reason    : e.message,
+                    line      : e.line || nt.line,
+                    character : e.character || nt.from
+                }, null);
+            }
+        }
+
+        for (i = 0; i < JSHINT.undefs.length; i += 1) {
+            k = JSHINT.undefs[i].slice(0);
+            scope = k.shift();
+            a = k[2];
+
+            if (typeof scope[a] !== 'string' && typeof funct[a] !== 'string') {
+                warning.apply(warning, k);
+            }
+        }
+
+        return JSHINT.errors.length === 0;
+    };
+
+    // Data summary.
+    itself.data = function () {
+
+        var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j,
+            members = [], n, unused = [], v;
+        if (itself.errors.length) {
+            data.errors = itself.errors;
+        }
+
+        if (jsonmode) {
+            data.json = true;
+        }
+
+        for (n in implied) {
+            if (is_own(implied, n)) {
+                implieds.push({
+                    name: n,
+                    line: implied[n]
+                });
+            }
+        }
+        if (implieds.length > 0) {
+            data.implieds = implieds;
+        }
+
+        if (urls.length > 0) {
+            data.urls = urls;
+        }
+
+        globals = Object.keys(scope);
+        if (globals.length > 0) {
+            data.globals = globals;
+        }
+
+        for (i = 1; i < functions.length; i += 1) {
+            f = functions[i];
+            fu = {};
+            for (j = 0; j < functionicity.length; j += 1) {
+                fu[functionicity[j]] = [];
+            }
+            for (n in f) {
+                if (is_own(f, n) && n.charAt(0) !== '(') {
+                    v = f[n];
+                    if (v === 'unction') {
+                        v = 'unused';
+                    }
+                    if (Array.isArray(fu[v])) {
+                        fu[v].push(n);
+                        if (v === 'unused') {
+                            unused.push({
+                                name: n,
+                                line: f['(line)'],
+                                'function': f['(name)']
+                            });
+                        }
+                    }
+                }
+            }
+            for (j = 0; j < functionicity.length; j += 1) {
+                if (fu[functionicity[j]].length === 0) {
+                    delete fu[functionicity[j]];
+                }
+            }
+            fu.name = f['(name)'];
+            fu.param = f['(params)'];
+            fu.line = f['(line)'];
+            fu.last = f['(last)'];
+            data.functions.push(fu);
+        }
+
+        if (unused.length > 0) {
+            data.unused = unused;
+        }
+
+        members = [];
+        for (n in member) {
+            if (typeof member[n] === 'number') {
+                data.member = member;
+                break;
+            }
+        }
+
+        return data;
+    };
+
+    itself.report = function (option) {
+        var data = itself.data();
+
+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
+
+        function detail(h, array) {
+            var b, i, singularity;
+            if (array) {
+                o.push('<div><i>' + h + '</i> ');
+                array = array.sort();
+                for (i = 0; i < array.length; i += 1) {
+                    if (array[i] !== singularity) {
+                        singularity = array[i];
+                        o.push((b ? ', ' : '') + singularity);
+                        b = true;
+                    }
+                }
+                o.push('</div>');
+            }
+        }
+
+
+        if (data.errors || data.implieds || data.unused) {
+            err = true;
+            o.push('<div id=errors><i>Error:</i>');
+            if (data.errors) {
+                for (i = 0; i < data.errors.length; i += 1) {
+                    c = data.errors[i];
+                    if (c) {
+                        e = c.evidence || '';
+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
+                                c.line + ' character ' + c.character : '') +
+                                ': ' + c.reason.entityify() +
+                                '</p><p class=evidence>' +
+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
+                                e).entityify()) + '</p>');
+                    }
+                }
+            }
+
+            if (data.implieds) {
+                s = [];
+                for (i = 0; i < data.implieds.length; i += 1) {
+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
+                        data.implieds[i].line + '</i>';
+                }
+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
+            }
+
+            if (data.unused) {
+                s = [];
+                for (i = 0; i < data.unused.length; i += 1) {
+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
+                        data.unused[i].line + '</i> <code>' +
+                        data.unused[i]['function'] + '</code>';
+                }
+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
+            }
+            if (data.json) {
+                o.push('<p>JSON: bad.</p>');
+            }
+            o.push('</div>');
+        }
+
+        if (!option) {
+
+            o.push('<br><div id=functions>');
+
+            if (data.urls) {
+                detail("URLs<br>", data.urls, '<br>');
+            }
+
+            if (data.json && !err) {
+                o.push('<p>JSON: good.</p>');
+            } else if (data.globals) {
+                o.push('<div><i>Global</i> ' +
+                        data.globals.sort().join(', ') + '</div>');
+            } else {
+                o.push('<div><i>No new global variables introduced.</i></div>');
+            }
+
+            for (i = 0; i < data.functions.length; i += 1) {
+                f = data.functions[i];
+
+                o.push('<br><div class=function><i>' + f.line + '-' +
+                        f.last + '</i> ' + (f.name || '') + '(' +
+                        (f.param ? f.param.join(', ') : '') + ')</div>');
+                detail('<big><b>Unused</b></big>', f.unused);
+                detail('Closure', f.closure);
+                detail('Variable', f['var']);
+                detail('Exception', f.exception);
+                detail('Outer', f.outer);
+                detail('Global', f.global);
+                detail('Label', f.label);
+            }
+
+            if (data.member) {
+                a = Object.keys(data.member);
+                if (a.length) {
+                    a = a.sort();
+                    m = '<br><pre id=members>/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '<br>');
+                            m = '    ';
+                            l = 1;
+                        }
+                        l += n.length + 2;
+                        if (data.member[k] === 1) {
+                            n = '<i>' + n + '</i>';
+                        }
+                        if (i < a.length - 1) {
+                            n += ', ';
+                        }
+                        m += n;
+                    }
+                    o.push(m + '<br>*/</pre>');
+                }
+                o.push('</div>');
+            }
+        }
+        return o.join('');
+    };
+
+    itself.jshint = itself;
+    itself.edition = '2011-04-16';
+
+    return itself;
+}());
+
+// Make JSHINT a Node module, if possible.
+if (typeof exports === 'object' && exports)
+    exports.JSHINT = JSHINT;
+/*jshint boss: true, rhino: true */
+/*globals JSHINT*/
+
+(function (args) {
+    var filenames = [],
+        optstr, // arg1=val1,arg2=val2,...
+        predef, // global1=override,global2,global3,...
+        opts   = { rhino: true },
+        retval = 0;
+
+    args.forEach(function (arg) {
+        if (arg.indexOf("=") > -1) {
+            //first time it's the options
+            if (!optstr) {
+                optstr = arg;
+            } else if (!predef) {
+                predef = arg;
+            }
+        } else {
+            filenames.push(arg);
+        }
+    });
+
+    if (filenames.length === 0) {
+        print('Usage: jshint.js file.js');
+        quit(1);
+    }
+
+    if (optstr) {
+        optstr.split(',').forEach(function (arg) {
+            var o = arg.split('=');
+            opts[o[0]] = (function (ov) {
+                switch (ov) {
+                case 'true':
+                    return true;
+                case 'false':
+                    return false;
+                default:
+                    return ov;
+                }
+            }(o[1]));
+        });
+    }
+
+    if (predef) {
+        opts.predef = {};
+        predef.split(',').forEach(function (arg) {
+            var global = arg.split('=');
+            opts.predef[global[0]] = (function (override) {
+                return (override === 'false') ? false : true;
+            }(global[1]));
+        });
+    }
+
+    filenames.forEach(function (name) {
+
+        var input = readFile(name);
+
+        if (!input) {
+            print('jshint: Couldn\'t open file ' + name);
+            quit(1);
+        }
+
+        if (!JSHINT(input, opts)) {
+            for (var i = 0, err; err = JSHINT.errors[i]; i += 1) {
+                print(err.reason + ' (' + name + ':' + err.line + ':' + err.character + ')');
+                print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
+                print('');
+            }
+            retval = 1;
+        }
+    });
+
+    quit(retval);
+}(arguments));
diff --git a/jshint/env/jsc.js b/jshint/env/jsc.js
new file mode 100644 (file)
index 0000000..94799c9
--- /dev/null
@@ -0,0 +1,79 @@
+/*jshint boss:true, evil:true */
+
+// usage:
+//   jsc ${env_home}/jsc.js -- ${file} "$(cat ${file})" "{option1:true,option2:false} ${env_home}"
+var env_home = '';
+if (arguments.length > 3) {
+  env_home = arguments[3].toString().replace(/\/env$/, '/');
+}
+load(env_home + "jshint.js");
+
+if (typeof(JSHINT) === 'undefined') {
+  print('jshint: Could not load jshint.js, tried "' + env_home + 'jshint.js".');
+  quit();
+}
+
+(function(args){
+    var home  = args[3],
+        name  = args[0],
+        input = args[1],
+        opts  = (function(arg){
+            var opts = {};
+            var item;
+
+            switch (arg) {
+            case undefined:
+            case '':
+                return opts;
+            default:
+                arg = arg.split(',');
+                for (var i = 0, ii = arg.length; i < ii; i++) {
+                    item = arg[i].split(':');
+                    opts[item[0]] = eval(item[1]);
+                }
+                return opts;
+            }
+        })(args[2]);
+
+    if (!name) {
+        print('jshint: No file name was provided.');
+        quit();
+    }
+
+    if (!input) {
+        print('jshint: ' + name + ' contents were not provided to jshint.');
+        quit();
+    }
+
+    if (!JSHINT(input, opts)) {
+        for (var i = 0, err; err = JSHINT.errors[i]; i++) {
+            print(err.reason + ' (line: ' + err.line + ', character: ' + err.character + ')');
+            print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
+            print('');
+        }
+    }
+
+    var data = JSHINT.data();
+    if (data.unused !== undefined) {
+      for (var i = 0, unused; unused = data.unused[i]; i++) {
+        print('Unused variable "' + unused.name + '" (line: ' + unused.line + ')');
+      }
+    }
+
+    // if (data.globals !== undefined) {
+    //   for (var i = 0, globals; global = data.globals[i]; i++) {
+    //     print('Global variable "' + global + '"');
+    //   }
+    // }
+
+    if (data.implieds !== undefined) {
+      for (var i = 0, implied; implied = data.implieds[i]; i++) {
+        print('Implied global variable "' + implied.name + '" (line: ' + implied.line + ')');
+      }
+    }
+
+    // print('Errors: ' + JSHINT.errors.length);
+    // print(JSHINT.report(true));
+
+    quit();
+})(arguments);
diff --git a/jshint/env/jsc.sh b/jshint/env/jsc.sh
new file mode 100755 (executable)
index 0000000..e5baf50
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+# usage (run from any directory) :
+#   env/jsc.sh /path/to/script.js
+# or with jshint options:
+#   env/jsc.sh /path/to/script.js "{option1:true,option2:false,option3:25}"
+
+alias jsc="/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"
+FILE="${1}"
+OPTS="${2}"
+
+FILE_CONTENT=$(cat "${FILE}")
+
+if [ -L $BASH_SOURCE ]; then
+  ENV_HOME="$( cd "$( dirname "$(readlink "$BASH_SOURCE")" )" && pwd )"
+else
+  ENV_HOME="$( cd "$( dirname "$BASH_SOURCE" )" && pwd )"
+fi
+
+LINT_RESULT=$(jsc "${ENV_HOME}"/jsc.js -- "${FILE}" "${FILE_CONTENT}" "${OPTS}" "${ENV_HOME}")
+
+ERRORS=$(echo ${LINT_RESULT} | egrep [^\s] -c)
+
+if [[ ${ERRORS} -ne 0 ]]; then
+       echo "[jshint] Error(s) in ${FILE}:"
+       printf "%s\n" "${LINT_RESULT}"
+else
+       echo "[jshint] ${FILE} passed!"
+fi
+
+exit $((0 + ${ERRORS}))
diff --git a/jshint/env/rhino.js b/jshint/env/rhino.js
new file mode 100644 (file)
index 0000000..f0529cd
--- /dev/null
@@ -0,0 +1,75 @@
+/*jshint boss: true, rhino: true */
+/*globals JSHINT*/
+
+(function (args) {
+    var filenames = [],
+        optstr, // arg1=val1,arg2=val2,...
+        predef, // global1=override,global2,global3,...
+        opts   = { rhino: true },
+        retval = 0;
+
+    args.forEach(function (arg) {
+        if (arg.indexOf("=") > -1) {
+            //first time it's the options
+            if (!optstr) {
+                optstr = arg;
+            } else if (!predef) {
+                predef = arg;
+            }
+        } else {
+            filenames.push(arg);
+        }
+    });
+
+    if (filenames.length === 0) {
+        print('Usage: jshint.js file.js');
+        quit(1);
+    }
+
+    if (optstr) {
+        optstr.split(',').forEach(function (arg) {
+            var o = arg.split('=');
+            opts[o[0]] = (function (ov) {
+                switch (ov) {
+                case 'true':
+                    return true;
+                case 'false':
+                    return false;
+                default:
+                    return ov;
+                }
+            }(o[1]));
+        });
+    }
+
+    if (predef) {
+        opts.predef = {};
+        predef.split(',').forEach(function (arg) {
+            var global = arg.split('=');
+            opts.predef[global[0]] = (function (override) {
+                return (override === 'false') ? false : true;
+            }(global[1]));
+        });
+    }
+
+    filenames.forEach(function (name) {
+
+        var input = readFile(name);
+
+        if (!input) {
+            print('jshint: Couldn\'t open file ' + name);
+            quit(1);
+        }
+
+        if (!JSHINT(input, opts)) {
+            for (var i = 0, err; err = JSHINT.errors[i]; i += 1) {
+                print(err.reason + ' (' + name + ':' + err.line + ':' + err.character + ')');
+                print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
+                print('');
+            }
+            retval = 1;
+        }
+    });
+
+    quit(retval);
+}(arguments));
diff --git a/jshint/env/wsh.js b/jshint/env/wsh.js
new file mode 100644 (file)
index 0000000..d9efb6a
--- /dev/null
@@ -0,0 +1,181 @@
+/*jshint evil: true, shadow: true, wsh: true */
+/*global JSHINT: false */
+
+(function() {
+       function readFile(path, charset) {
+               try {
+                       var stream = WScript.CreateObject("ADODB.Stream");
+
+                       stream.Charset = charset;
+                       stream.Open();
+                       stream.LoadFromFile(path);
+
+                       var result = stream.ReadText();
+                       stream.close();
+
+                       return result;
+               } catch (ex) {
+                       return null;
+               }
+       }
+
+       var formatters = {
+               errors: function(errors, lines) {
+                       for (var i = 0; i < errors.length; i++) {
+                               var error = errors[i];
+
+                               if (!error) continue;
+
+                               if (i) lines.push("");
+
+                               lines.push("Line " + error.line + " character " + error.character + ": " + error.reason);
+
+                               if (error.evidence) lines.push("    " + error.evidence.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, "$1"));
+                       }
+               },
+
+               implieds: function(implieds, lines) {
+                       lines.push("Implied globals:");
+
+                       var globals = {};
+
+                       for (var i = 0; i < implieds.length; i++) {
+                               var item = implieds[i];
+
+                               if (!(item.name in globals)) globals[item.name] = [];
+
+                               globals[item.name].push(item.line);
+                       }
+
+                       for (var name in globals) {
+                               lines.push("    " + name + ": " + globals[name].join(", "));
+                       }
+               },
+
+               unused: function(unused, lines) {
+                       lines.push("Unused variables:");
+
+                       var func, names = {};
+
+                       for (var i = 0; i < unused.length; i++) {
+                               var item = unused[i];
+
+                               func = item["function"];
+
+                               if (!(func in names)) names[func] = [];
+
+                               names[func].push(item.name + " (" + item.line + ")");
+                       }
+
+                       for (func in names) {
+                               lines.push("    " + func + ": " + names[func].join(", "));
+                       }
+               }
+       };
+
+       var scriptName = WScript.ScriptName;
+       var scriptPath = WScript.ScriptFullName;
+
+       scriptPath = scriptPath.substr(0, scriptPath.length - scriptName.length);
+
+       // load JSHint if the two scripts have not been concatenated
+       if (typeof JSHINT === "undefined") {
+               eval(readFile(scriptPath + "..\\jshint.js", 'utf-8'));
+
+               if (typeof JSHINT === "undefined") {
+                       WScript.StdOut.WriteLine("ERROR: Could not find 'jshint.js'.");
+
+                       WScript.Quit(-2);
+               }
+       }
+
+       var globals = {};
+       var options = {};
+       var named = WScript.Arguments.Named;
+       var unnamed = WScript.Arguments.Unnamed;
+
+       if (unnamed.length !== 1) {
+               WScript.StdOut.WriteLine("    usage: cscript " + scriptName + " [options] <script>");
+               WScript.StdOut.WriteLine("");
+               WScript.StdOut.WriteLine("Scans the specified script with JSHint and reports any errors encountered.  If");
+               WScript.StdOut.WriteLine("the script name is \"-\", it will be read from standard input instead.");
+               WScript.StdOut.WriteLine("");
+               WScript.StdOut.WriteLine("JSHint configuration options can be passed in via optional, Windows-style");
+               WScript.StdOut.WriteLine("arguments.  For example:");
+               WScript.StdOut.WriteLine("    cscript " + scriptName + " /jquery:true myscript.js");
+               WScript.StdOut.WriteLine("    cscript " + scriptName + " /global:QUnit:false,_:false,foo:true foo.js");
+               WScript.StdOut.WriteLine("");
+               WScript.StdOut.WriteLine("By default, we assume that your file is encoded if UTF-8. You can change that");
+               WScript.StdOut.WriteLine("by providing a custom charset option:");
+               WScript.StdOut.WriteLine("    cscript " + scriptName + " /charset:ascii myscript.js");
+
+               WScript.Quit(-1);
+       }
+
+       var script = unnamed(0);
+
+       if (script === "-") {
+               try {
+                       script = WScript.StdIn.ReadAll();
+               } catch (ex) {
+                       script = null;
+               }
+       } else {
+               script = readFile(script, named('charset') || 'utf-8');
+       }
+
+       if (script === null) {
+               WScript.StdOut.WriteLine("ERROR: Could not read target script.");
+
+               WScript.Quit(2);
+       }
+
+       for (var etor = new Enumerator(named); !etor.atEnd(); etor.moveNext()) {
+               var option = etor.item();
+               var value = named(option);
+
+               if (option === "global") {
+                       value = value.split(",");
+
+                       for (var i = 0; i < value.length; i++) {
+                               var name = value[i].split(":");
+
+                               if (name.length === 1 || name[1] === "false") {
+                                       globals[name[0]] = false;
+                               } else if (name[1] === "true") {
+                                       globals[name[0]] = true;
+                               } else {
+                                       WScript.StdOut.WriteLine("Unrecognized value for global: " + name[0]);
+                                       WScript.StdOut.WriteLine("Must be \"true\", \"false\", or omitted.");
+
+                                       WScript.Quit(-1);
+                               }
+                       }
+               } else {
+                       options[option] = value === "true" ? true : value === "false" ? false : value;
+               }
+       }
+
+       JSHINT(script, options, globals);
+
+       var data = JSHINT.data();
+       var lines = [];
+
+       for (var formatter in formatters) {
+               if (data[formatter]) {
+                       if (lines.length) lines.push("");
+
+                       formatters[formatter](data[formatter], lines);
+               }
+       }
+
+       if (lines.length) {
+               for (var i = 0; i < lines.length; i++) {
+                       WScript.StdOut.WriteLine(lines[i]);
+               }
+
+               WScript.Quit(1);
+       } else {
+               WScript.Quit(0);
+       }
+}());
diff --git a/jshint/jshint.js b/jshint/jshint.js
new file mode 100644 (file)
index 0000000..4be801d
--- /dev/null
@@ -0,0 +1,4314 @@
+/*!
+ * JSHint, by JSHint Community.
+ *
+ * Licensed under the same slightly modified MIT license that JSLint is.
+ * It stops evil-doers everywhere.
+ *
+ * JSHint is a derivative work of JSLint:
+ *
+ *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
+ *
+ *   Permission is hereby granted, free of charge, to any person obtaining
+ *   a copy of this software and associated documentation files (the "Software"),
+ *   to deal in the Software without restriction, including without limitation
+ *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ *   and/or sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following conditions:
+ *
+ *   The above copyright notice and this permission notice shall be included
+ *   in all copies or substantial portions of the Software.
+ *
+ *   The Software shall be used for Good, not Evil.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ *   DEALINGS IN THE SOFTWARE.
+ *
+ * JSHint was forked from 2010-12-16 edition of JSLint.
+ *
+ */
+
+/*
+ JSHINT is a global function. It takes two parameters.
+
+     var myResult = JSHINT(source, option);
+
+ The first parameter is either a string or an array of strings. If it is a
+ string, it will be split on '\n' or '\r'. If it is an array of strings, it
+ is assumed that each string represents one line. The source can be a
+ JavaScript text or a JSON text.
+
+ The second parameter is an optional object of options which control the
+ operation of JSHINT. Most of the options are booleans: They are all
+ optional and have a default value of false. One of the options, predef,
+ can be an array of names, which will be used to declare global variables,
+ or an object whose keys are used as global names, with a boolean value
+ that determines if they are assignable.
+
+ If it checks out, JSHINT returns true. Otherwise, it returns false.
+
+ If false, you can inspect JSHINT.errors to find out the problems.
+ JSHINT.errors is an array of objects containing these members:
+
+ {
+     line      : The line (relative to 0) at which the lint was found
+     character : The character (relative to 0) at which the lint was found
+     reason    : The problem
+     evidence  : The text line in which the problem occurred
+     raw       : The raw message before the details were inserted
+     a         : The first detail
+     b         : The second detail
+     c         : The third detail
+     d         : The fourth detail
+ }
+
+ If a fatal error was found, a null will be the last element of the
+ JSHINT.errors array.
+
+ You can request a Function Report, which shows all of the functions
+ and the parameters and vars that they use. This can be used to find
+ implied global variables and other problems. The report is in HTML and
+ can be inserted in an HTML <body>.
+
+     var myReport = JSHINT.report(limited);
+
+ If limited is true, then the report will be limited to only errors.
+
+ You can request a data structure which contains JSHint's results.
+
+     var myData = JSHINT.data();
+
+ It returns a structure with this form:
+
+ {
+     errors: [
+         {
+             line: NUMBER,
+             character: NUMBER,
+             reason: STRING,
+             evidence: STRING
+         }
+     ],
+     functions: [
+         name: STRING,
+         line: NUMBER,
+         last: NUMBER,
+         param: [
+             STRING
+         ],
+         closure: [
+             STRING
+         ],
+         var: [
+             STRING
+         ],
+         exception: [
+             STRING
+         ],
+         outer: [
+             STRING
+         ],
+         unused: [
+             STRING
+         ],
+         global: [
+             STRING
+         ],
+         label: [
+             STRING
+         ]
+     ],
+     globals: [
+         STRING
+     ],
+     member: {
+         STRING: NUMBER
+     },
+     unuseds: [
+         {
+             name: STRING,
+             line: NUMBER
+         }
+     ],
+     implieds: [
+         {
+             name: STRING,
+             line: NUMBER
+         }
+     ],
+     urls: [
+         STRING
+     ],
+     json: BOOLEAN
+ }
+
+ Empty arrays will not be included.
+
+*/
+
+/*jshint
+ evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true,
+ undef: true, maxlen: 100, indent:4
+*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)",
+ "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)",
+ "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)",
+ "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==",
+ "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax,
+ __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio,
+ Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas,
+ CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date,
+ Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag,
+ E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event,
+ Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form,
+ FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey,
+ HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement,
+ HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement,
+ HTMLDivElement, HTMLDListElement, HTMLFieldSetElement,
+ HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement,
+ HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement,
+ HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement,
+ HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement,
+ HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement,
+ HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement,
+ HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement,
+ HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement,
+ HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement,
+ HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement,
+ HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement,
+ Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array,
+ Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E,
+ MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native,
+ NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI,
+ POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError,
+ Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation,
+ SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion,
+ ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller,
+ Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables,
+ SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template,
+ Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL,
+ VBArray, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator,
+ XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a,
+ addEventListener, address, alert, apply, applicationCache, arguments, arity,
+ asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee,
+ caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout,
+ close, closed, closure, comment, condition, confirm, console, constructor,
+ content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI,
+ decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document,
+ dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent,
+ entityify, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil,
+ ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus,
+ forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions,
+ g, gc, getComputedStyle, getRow, getter, GLOBAL, global, globals, globalstrict,
+ hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include,
+ indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray,
+ isDigit, isFinite, isNaN, iterator, java, join, jshint,
+ JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak,
+ latedef, lbp, led, left, length, line, load, loadClass, localStorage, location,
+ log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy,
+ moveTo, mootools, multistr, name, navigator, new, newcap, noarg, node, noempty, nomen,
+ nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus,
+ onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param,
+ parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt,
+ proto, prototype, prototypejs, provides, push, quit, range, raw, reach, reason, regexp,
+ readFile, readUrl, regexdash, removeEventListener, replace, report, require,
+ reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right,
+ runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal,
+ send, serialize, sessionStorage, setInterval, setTimeout, setter, setterToken, shift, slice,
+ smarttabs, sort, spawn, split, stack, status, start, strict, sub, substr, supernew, shadow,
+ supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing,
+ type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef, undefs, unused, urls, validthis,
+ value, valueOf, var, version, WebSocket, white, window, Worker, wsh*/
+
+/*global exports: false */
+
+// We build the application inside a function so that we produce only a single
+// global variable. That function will be invoked immediately, and its return
+// value is the JSHINT function itself.
+
+var JSHINT = (function () {
+    "use strict";
+
+    var anonname,       // The guessed name for anonymous functions.
+
+// These are operators that should not be used with the ! operator.
+
+        bang = {
+            '<'  : true,
+            '<=' : true,
+            '==' : true,
+            '===': true,
+            '!==': true,
+            '!=' : true,
+            '>'  : true,
+            '>=' : true,
+            '+'  : true,
+            '-'  : true,
+            '*'  : true,
+            '/'  : true,
+            '%'  : true
+        },
+
+        // These are the JSHint boolean options.
+        boolOptions = {
+            asi         : true, // if automatic semicolon insertion should be tolerated
+            bitwise     : true, // if bitwise operators should not be allowed
+            boss        : true, // if advanced usage of assignments should be allowed
+            browser     : true, // if the standard browser globals should be predefined
+            couch       : true, // if CouchDB globals should be predefined
+            curly       : true, // if curly braces around all blocks should be required
+            debug       : true, // if debugger statements should be allowed
+            devel       : true, // if logging globals should be predefined (console,
+                                // alert, etc.)
+            dojo        : true, // if Dojo Toolkit globals should be predefined
+            eqeqeq      : true, // if === should be required
+            eqnull      : true, // if == null comparisons should be tolerated
+            es5         : true, // if ES5 syntax should be allowed
+            esnext      : true, // if es.next specific syntax should be allowed
+            evil        : true, // if eval should be allowed
+            expr        : true, // if ExpressionStatement should be allowed as Programs
+            forin       : true, // if for in statements must filter
+            funcscope   : true, // if only function scope should be used for scope tests
+            globalstrict: true, // if global "use strict"; should be allowed (also
+                                // enables 'strict')
+            immed       : true, // if immediate invocations must be wrapped in parens
+            iterator    : true, // if the `__iterator__` property should be allowed
+            jquery      : true, // if jQuery globals should be predefined
+            lastsemic   : true, // if semicolons may be ommitted for the trailing
+                                // statements inside of a one-line blocks.
+            latedef     : true, // if the use before definition should not be tolerated
+            laxbreak    : true, // if line breaks should not be checked
+            loopfunc    : true, // if functions should be allowed to be defined within
+                                // loops
+            mootools    : true, // if MooTools globals should be predefined
+            multistr    : true, // allow multiline strings
+            newcap      : true, // if constructor names must be capitalized
+            noarg       : true, // if arguments.caller and arguments.callee should be
+                                // disallowed
+            node        : true, // if the Node.js environment globals should be
+                                // predefined
+            noempty     : true, // if empty blocks should be disallowed
+            nonew       : true, // if using `new` for side-effects should be disallowed
+            nonstandard : true, // if non-standard (but widely adopted) globals should
+                                // be predefined
+            nomen       : true, // if names should be checked
+            onevar      : true, // if only one var statement per function should be
+                                // allowed
+            onecase     : true, // if one case switch statements should be allowed
+            passfail    : true, // if the scan should stop on first error
+            plusplus    : true, // if increment/decrement should not be allowed
+            proto       : true, // if the `__proto__` property should be allowed
+            prototypejs : true, // if Prototype and Scriptaculous globals should be
+                                // predefined
+            regexdash   : true, // if unescaped first/last dash (-) inside brackets
+                                // should be tolerated
+            regexp      : true, // if the . should not be allowed in regexp literals
+            rhino       : true, // if the Rhino environment globals should be predefined
+            undef       : true, // if variables should be declared before used
+            scripturl   : true, // if script-targeted URLs should be tolerated
+            shadow      : true, // if variable shadowing should be tolerated
+            smarttabs   : true, // if smarttabs should be tolerated
+                                // (http://www.emacswiki.org/emacs/SmartTabs)
+            strict      : true, // require the "use strict"; pragma
+            sub         : true, // if all forms of subscript notation are tolerated
+            supernew    : true, // if `new function () { ... };` and `new Object;`
+                                // should be tolerated
+            trailing    : true, // if trailing whitespace rules apply
+            validthis   : true, // if 'this' inside a non-constructor function is valid.
+                                // This is a function scoped option only.
+            white       : true, // if strict whitespace rules apply
+            wsh         : true  // if the Windows Scripting Host environment globals
+                                // should be predefined
+        },
+
+        // browser contains a set of global names which are commonly provided by a
+        // web browser environment.
+        browser = {
+            ArrayBuffer              :  false,
+            ArrayBufferView          :  false,
+            Audio                    :  false,
+            addEventListener         :  false,
+            applicationCache         :  false,
+            blur                     :  false,
+            clearInterval            :  false,
+            clearTimeout             :  false,
+            close                    :  false,
+            closed                   :  false,
+            DataView                 :  false,
+            defaultStatus            :  false,
+            document                 :  false,
+            event                    :  false,
+            FileReader               :  false,
+            Float32Array             :  false,
+            Float64Array             :  false,
+            FormData                 :  false,
+            focus                    :  false,
+            frames                   :  false,
+            getComputedStyle         :  false,
+            HTMLElement              :  false,
+            HTMLAnchorElement        :  false,
+            HTMLBaseElement          :  false,
+            HTMLBlockquoteElement    :  false,
+            HTMLBodyElement          :  false,
+            HTMLBRElement            :  false,
+            HTMLButtonElement        :  false,
+            HTMLCanvasElement        :  false,
+            HTMLDirectoryElement     :  false,
+            HTMLDivElement           :  false,
+            HTMLDListElement         :  false,
+            HTMLFieldSetElement      :  false,
+            HTMLFontElement          :  false,
+            HTMLFormElement          :  false,
+            HTMLFrameElement         :  false,
+            HTMLFrameSetElement      :  false,
+            HTMLHeadElement          :  false,
+            HTMLHeadingElement       :  false,
+            HTMLHRElement            :  false,
+            HTMLHtmlElement          :  false,
+            HTMLIFrameElement        :  false,
+            HTMLImageElement         :  false,
+            HTMLInputElement         :  false,
+            HTMLIsIndexElement       :  false,
+            HTMLLabelElement         :  false,
+            HTMLLayerElement         :  false,
+            HTMLLegendElement        :  false,
+            HTMLLIElement            :  false,
+            HTMLLinkElement          :  false,
+            HTMLMapElement           :  false,
+            HTMLMenuElement          :  false,
+            HTMLMetaElement          :  false,
+            HTMLModElement           :  false,
+            HTMLObjectElement        :  false,
+            HTMLOListElement         :  false,
+            HTMLOptGroupElement      :  false,
+            HTMLOptionElement        :  false,
+            HTMLParagraphElement     :  false,
+            HTMLParamElement         :  false,
+            HTMLPreElement           :  false,
+            HTMLQuoteElement         :  false,
+            HTMLScriptElement        :  false,
+            HTMLSelectElement        :  false,
+            HTMLStyleElement         :  false,
+            HTMLTableCaptionElement  :  false,
+            HTMLTableCellElement     :  false,
+            HTMLTableColElement      :  false,
+            HTMLTableElement         :  false,
+            HTMLTableRowElement      :  false,
+            HTMLTableSectionElement  :  false,
+            HTMLTextAreaElement      :  false,
+            HTMLTitleElement         :  false,
+            HTMLUListElement         :  false,
+            HTMLVideoElement         :  false,
+            history                  :  false,
+            Int16Array               :  false,
+            Int32Array               :  false,
+            Int8Array                :  false,
+            Image                    :  false,
+            length                   :  false,
+            localStorage             :  false,
+            location                 :  false,
+            moveBy                   :  false,
+            moveTo                   :  false,
+            name                     :  false,
+            navigator                :  false,
+            onbeforeunload           :  true,
+            onblur                   :  true,
+            onerror                  :  true,
+            onfocus                  :  true,
+            onload                   :  true,
+            onresize                 :  true,
+            onunload                 :  true,
+            open                     :  false,
+            openDatabase             :  false,
+            opener                   :  false,
+            Option                   :  false,
+            parent                   :  false,
+            print                    :  false,
+            removeEventListener      :  false,
+            resizeBy                 :  false,
+            resizeTo                 :  false,
+            screen                   :  false,
+            scroll                   :  false,
+            scrollBy                 :  false,
+            scrollTo                 :  false,
+            sessionStorage           :  false,
+            setInterval              :  false,
+            setTimeout               :  false,
+            SharedWorker             :  false,
+            status                   :  false,
+            top                      :  false,
+            Uint16Array              :  false,
+            Uint32Array              :  false,
+            Uint8Array               :  false,
+            WebSocket                :  false,
+            window                   :  false,
+            Worker                   :  false,
+            XMLHttpRequest           :  false,
+            XPathEvaluator           :  false,
+            XPathException           :  false,
+            XPathExpression          :  false,
+            XPathNamespace           :  false,
+            XPathNSResolver          :  false,
+            XPathResult              :  false
+        },
+
+        couch = {
+            "require" : false,
+            respond   : false,
+            getRow    : false,
+            emit      : false,
+            send      : false,
+            start     : false,
+            sum       : false,
+            log       : false,
+            exports   : false,
+            module    : false,
+            provides  : false
+        },
+
+        devel = {
+            alert   : false,
+            confirm : false,
+            console : false,
+            Debug   : false,
+            opera   : false,
+            prompt  : false
+        },
+
+        dojo = {
+            dojo      : false,
+            dijit     : false,
+            dojox     : false,
+            define    : false,
+            "require" : false
+        },
+
+        escapes = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '/' : '\\/',
+            '\\': '\\\\'
+        },
+
+        funct,          // The current function
+
+        functionicity = [
+            'closure', 'exception', 'global', 'label',
+            'outer', 'unused', 'var'
+        ],
+
+        functions,      // All of the functions
+
+        global,         // The global scope
+        implied,        // Implied globals
+        inblock,
+        indent,
+        jsonmode,
+
+        jquery = {
+            '$'    : false,
+            jQuery : false
+        },
+
+        lines,
+        lookahead,
+        member,
+        membersOnly,
+
+        mootools = {
+            '$'             : false,
+            '$$'            : false,
+            Assets          : false,
+            Browser         : false,
+            Chain           : false,
+            Class           : false,
+            Color           : false,
+            Cookie          : false,
+            Core            : false,
+            Document        : false,
+            DomReady        : false,
+            DOMReady        : false,
+            Drag            : false,
+            Element         : false,
+            Elements        : false,
+            Event           : false,
+            Events          : false,
+            Fx              : false,
+            Group           : false,
+            Hash            : false,
+            HtmlTable       : false,
+            Iframe          : false,
+            IframeShim      : false,
+            InputValidator  : false,
+            instanceOf      : false,
+            Keyboard        : false,
+            Locale          : false,
+            Mask            : false,
+            MooTools        : false,
+            Native          : false,
+            Options         : false,
+            OverText        : false,
+            Request         : false,
+            Scroller        : false,
+            Slick           : false,
+            Slider          : false,
+            Sortables       : false,
+            Spinner         : false,
+            Swiff           : false,
+            Tips            : false,
+            Type            : false,
+            typeOf          : false,
+            URI             : false,
+            Window          : false
+        },
+
+        nexttoken,
+
+        node = {
+            __filename    : false,
+            __dirname     : false,
+            Buffer        : false,
+            console       : false,
+            exports       : false,
+            GLOBAL        : false,
+            global        : false,
+            module        : false,
+            process       : false,
+            require       : false,
+            setTimeout    : false,
+            clearTimeout  : false,
+            setInterval   : false,
+            clearInterval : false
+        },
+
+        noreach,
+        option,
+        predefined,     // Global variables defined by option
+        prereg,
+        prevtoken,
+
+        prototypejs = {
+            '$'               : false,
+            '$$'              : false,
+            '$A'              : false,
+            '$F'              : false,
+            '$H'              : false,
+            '$R'              : false,
+            '$break'          : false,
+            '$continue'       : false,
+            '$w'              : false,
+            Abstract          : false,
+            Ajax              : false,
+            Class             : false,
+            Enumerable        : false,
+            Element           : false,
+            Event             : false,
+            Field             : false,
+            Form              : false,
+            Hash              : false,
+            Insertion         : false,
+            ObjectRange       : false,
+            PeriodicalExecuter: false,
+            Position          : false,
+            Prototype         : false,
+            Selector          : false,
+            Template          : false,
+            Toggle            : false,
+            Try               : false,
+            Autocompleter     : false,
+            Builder           : false,
+            Control           : false,
+            Draggable         : false,
+            Draggables        : false,
+            Droppables        : false,
+            Effect            : false,
+            Sortable          : false,
+            SortableObserver  : false,
+            Sound             : false,
+            Scriptaculous     : false
+        },
+
+        rhino = {
+            defineClass  : false,
+            deserialize  : false,
+            gc           : false,
+            help         : false,
+            importPackage: false,
+            "java"       : false,
+            load         : false,
+            loadClass    : false,
+            print        : false,
+            quit         : false,
+            readFile     : false,
+            readUrl      : false,
+            runCommand   : false,
+            seal         : false,
+            serialize    : false,
+            spawn        : false,
+            sync         : false,
+            toint32      : false,
+            version      : false
+        },
+
+        scope,      // The current scope
+        stack,
+
+        // standard contains the global names that are provided by the
+        // ECMAScript standard.
+        standard = {
+            Array               : false,
+            Boolean             : false,
+            Date                : false,
+            decodeURI           : false,
+            decodeURIComponent  : false,
+            encodeURI           : false,
+            encodeURIComponent  : false,
+            Error               : false,
+            'eval'              : false,
+            EvalError           : false,
+            Function            : false,
+            hasOwnProperty      : false,
+            isFinite            : false,
+            isNaN               : false,
+            JSON                : false,
+            Math                : false,
+            Number              : false,
+            Object              : false,
+            parseInt            : false,
+            parseFloat          : false,
+            RangeError          : false,
+            ReferenceError      : false,
+            RegExp              : false,
+            String              : false,
+            SyntaxError         : false,
+            TypeError           : false,
+            URIError            : false
+        },
+
+        // widely adopted global names that are not part of ECMAScript standard
+        nonstandard = {
+            escape              : false,
+            unescape            : false
+        },
+
+        standard_member = {
+            E                   : true,
+            LN2                 : true,
+            LN10                : true,
+            LOG2E               : true,
+            LOG10E              : true,
+            MAX_VALUE           : true,
+            MIN_VALUE           : true,
+            NEGATIVE_INFINITY   : true,
+            PI                  : true,
+            POSITIVE_INFINITY   : true,
+            SQRT1_2             : true,
+            SQRT2               : true
+        },
+
+        directive,
+        syntax = {},
+        tab,
+        token,
+        urls,
+        useESNextSyntax,
+        warnings,
+
+        wsh = {
+            ActiveXObject             : true,
+            Enumerator                : true,
+            GetObject                 : true,
+            ScriptEngine              : true,
+            ScriptEngineBuildVersion  : true,
+            ScriptEngineMajorVersion  : true,
+            ScriptEngineMinorVersion  : true,
+            VBArray                   : true,
+            WSH                       : true,
+            WScript                   : true,
+            XDomainRequest            : true
+        };
+
+    // Regular expressions. Some of these are stupidly long.
+    var ax, cx, tx, nx, nxg, lx, ix, jx, ft;
+    (function () {
+        /*jshint maxlen:300 */
+
+        // unsafe comment or string
+        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
+
+        // unsafe characters that are silently deleted by one or more browsers
+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+
+        // token
+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/;
+
+        // characters in strings that need escapement
+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+
+        // star slash
+        lx = /\*\/|\/\*/;
+
+        // identifier
+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
+
+        // javascript url
+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
+
+        // catches /* falls through */ comments
+        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;
+    }());
+
+    function F() {}     // Used by Object.create
+
+    function is_own(object, name) {
+
+// The object.hasOwnProperty method fails when the property under consideration
+// is named 'hasOwnProperty'. So we have to use this more convoluted form.
+
+        return Object.prototype.hasOwnProperty.call(object, name);
+    }
+
+// Provide critical ES5 functions to ES3.
+
+    if (typeof Array.isArray !== 'function') {
+        Array.isArray = function (o) {
+            return Object.prototype.toString.apply(o) === '[object Array]';
+        };
+    }
+
+    if (typeof Object.create !== 'function') {
+        Object.create = function (o) {
+            F.prototype = o;
+            return new F();
+        };
+    }
+
+    if (typeof Object.keys !== 'function') {
+        Object.keys = function (o) {
+            var a = [], k;
+            for (k in o) {
+                if (is_own(o, k)) {
+                    a.push(k);
+                }
+            }
+            return a;
+        };
+    }
+
+// Non standard methods
+
+    if (typeof String.prototype.entityify !== 'function') {
+        String.prototype.entityify = function () {
+            return this
+                .replace(/&/g, '&amp;')
+                .replace(/</g, '&lt;')
+                .replace(/>/g, '&gt;');
+        };
+    }
+
+    if (typeof String.prototype.isAlpha !== 'function') {
+        String.prototype.isAlpha = function () {
+            return (this >= 'a' && this <= 'z\uffff') ||
+                (this >= 'A' && this <= 'Z\uffff');
+        };
+    }
+
+    if (typeof String.prototype.isDigit !== 'function') {
+        String.prototype.isDigit = function () {
+            return (this >= '0' && this <= '9');
+        };
+    }
+
+    if (typeof String.prototype.supplant !== 'function') {
+        String.prototype.supplant = function (o) {
+            return this.replace(/\{([^{}]*)\}/g, function (a, b) {
+                var r = o[b];
+                return typeof r === 'string' || typeof r === 'number' ? r : a;
+            });
+        };
+    }
+
+    if (typeof String.prototype.name !== 'function') {
+        String.prototype.name = function () {
+
+// If the string looks like an identifier, then we can return it as is.
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can simply slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe
+// sequences.
+
+            if (ix.test(this)) {
+                return this;
+            }
+            if (nx.test(this)) {
+                return '"' + this.replace(nxg, function (a) {
+                    var c = escapes[a];
+                    if (c) {
+                        return c;
+                    }
+                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
+                }) + '"';
+            }
+            return '"' + this + '"';
+        };
+    }
+
+
+    function combine(t, o) {
+        var n;
+        for (n in o) {
+            if (is_own(o, n)) {
+                t[n] = o[n];
+            }
+        }
+    }
+
+    function assume() {
+        if (option.couch) {
+            combine(predefined, couch);
+        }
+
+        if (option.rhino) {
+            combine(predefined, rhino);
+        }
+
+        if (option.prototypejs) {
+            combine(predefined, prototypejs);
+        }
+
+        if (option.node) {
+            combine(predefined, node);
+        }
+
+        if (option.devel) {
+            combine(predefined, devel);
+        }
+
+        if (option.dojo) {
+            combine(predefined, dojo);
+        }
+
+        if (option.browser) {
+            combine(predefined, browser);
+        }
+
+        if (option.nonstandard) {
+            combine(predefined, nonstandard);
+        }
+
+        if (option.jquery) {
+            combine(predefined, jquery);
+        }
+
+        if (option.mootools) {
+            combine(predefined, mootools);
+        }
+
+        if (option.wsh) {
+            combine(predefined, wsh);
+        }
+
+        if (option.esnext) {
+            useESNextSyntax();
+        }
+
+        if (option.globalstrict && option.strict !== false) {
+            option.strict = true;
+        }
+    }
+
+
+    // Produce an error warning.
+    function quit(message, line, chr) {
+        var percentage = Math.floor((line / lines.length) * 100);
+
+        throw {
+            name: 'JSHintError',
+            line: line,
+            character: chr,
+            message: message + " (" + percentage + "% scanned).",
+            raw: message
+        };
+    }
+
+    function isundef(scope, m, t, a) {
+        return JSHINT.undefs.push([scope, m, t, a]);
+    }
+
+    function warning(m, t, a, b, c, d) {
+        var ch, l, w;
+        t = t || nexttoken;
+        if (t.id === '(end)') {  // `~
+            t = token;
+        }
+        l = t.line || 0;
+        ch = t.from || 0;
+        w = {
+            id: '(error)',
+            raw: m,
+            evidence: lines[l - 1] || '',
+            line: l,
+            character: ch,
+            a: a,
+            b: b,
+            c: c,
+            d: d
+        };
+        w.reason = m.supplant(w);
+        JSHINT.errors.push(w);
+        if (option.passfail) {
+            quit('Stopping. ', l, ch);
+        }
+        warnings += 1;
+        if (warnings >= option.maxerr) {
+            quit("Too many errors.", l, ch);
+        }
+        return w;
+    }
+
+    function warningAt(m, l, ch, a, b, c, d) {
+        return warning(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+    function error(m, t, a, b, c, d) {
+        var w = warning(m, t, a, b, c, d);
+    }
+
+    function errorAt(m, l, ch, a, b, c, d) {
+        return error(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+
+
+// lexical analysis and token construction
+
+    var lex = (function lex() {
+        var character, from, line, s;
+
+// Private lex methods
+
+        function nextLine() {
+            var at,
+                tw; // trailing whitespace check
+
+            if (line >= lines.length)
+                return false;
+
+            character = 1;
+            s = lines[line];
+            line += 1;
+
+            // If smarttabs option is used check for spaces followed by tabs only.
+            // Otherwise check for any occurence of mixed tabs and spaces.
+            if (option.smarttabs)
+                at = s.search(/ \t/);
+            else
+                at = s.search(/ \t|\t /);
+
+            if (at >= 0)
+                warningAt("Mixed spaces and tabs.", line, at + 1);
+
+            s = s.replace(/\t/g, tab);
+            at = s.search(cx);
+
+            if (at >= 0)
+                warningAt("Unsafe character.", line, at);
+
+            if (option.maxlen && option.maxlen < s.length)
+                warningAt("Line too long.", line, s.length);
+
+            // Check for trailing whitespaces
+            tw = /\s+$/.test(s);
+            if (option.trailing && tw && !/^\s+$/.test(s)) {
+                warningAt("Trailing whitespace.", line, tw);
+            }
+            return true;
+        }
+
+// Produce a token object.  The token inherits from a syntax symbol.
+
+        function it(type, value) {
+            var i, t;
+            if (type === '(color)' || type === '(range)') {
+                t = {type: type};
+            } else if (type === '(punctuator)' ||
+                    (type === '(identifier)' && is_own(syntax, value))) {
+                t = syntax[value] || syntax['(error)'];
+            } else {
+                t = syntax[type];
+            }
+            t = Object.create(t);
+            if (type === '(string)' || type === '(range)') {
+                if (!option.scripturl && jx.test(value)) {
+                    warningAt("Script URL.", line, from);
+                }
+            }
+            if (type === '(identifier)') {
+                t.identifier = true;
+                if (value === '__proto__' && !option.proto) {
+                    warningAt("The '{a}' property is deprecated.",
+                        line, from, value);
+                } else if (value === '__iterator__' && !option.iterator) {
+                    warningAt("'{a}' is only available in JavaScript 1.7.",
+                        line, from, value);
+                } else if (option.nomen && (value.charAt(0) === '_' ||
+                         value.charAt(value.length - 1) === '_')) {
+                    if (!option.node || token.id === '.' ||
+                            (value !== '__dirname' && value !== '__filename')) {
+                        warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value);
+                    }
+                }
+            }
+            t.value = value;
+            t.line = line;
+            t.character = character;
+            t.from = from;
+            i = t.id;
+            if (i !== '(endline)') {
+                prereg = i &&
+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
+                    i === 'return' ||
+                    i === 'case');
+            }
+            return t;
+        }
+
+        // Public lex methods
+        return {
+            init: function (source) {
+                if (typeof source === 'string') {
+                    lines = source
+                        .replace(/\r\n/g, '\n')
+                        .replace(/\r/g, '\n')
+                        .split('\n');
+                } else {
+                    lines = source;
+                }
+
+                // If the first line is a shebang (#!), make it a blank and move on.
+                // Shebangs are used by Node scripts.
+                if (lines[0] && lines[0].substr(0, 2) === '#!')
+                    lines[0] = '';
+
+                line = 0;
+                nextLine();
+                from = 1;
+            },
+
+            range: function (begin, end) {
+                var c, value = '';
+                from = character;
+                if (s.charAt(0) !== begin) {
+                    errorAt("Expected '{a}' and instead saw '{b}'.",
+                            line, character, begin, s.charAt(0));
+                }
+                for (;;) {
+                    s = s.slice(1);
+                    character += 1;
+                    c = s.charAt(0);
+                    switch (c) {
+                    case '':
+                        errorAt("Missing '{a}'.", line, character, c);
+                        break;
+                    case end:
+                        s = s.slice(1);
+                        character += 1;
+                        return it('(range)', value);
+                    case '\\':
+                        warningAt("Unexpected '{a}'.", line, character, c);
+                    }
+                    value += c;
+                }
+
+            },
+
+
+            // token -- this is called by advance to get the next token
+            token: function () {
+                var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange;
+
+                function match(x) {
+                    var r = x.exec(s), r1;
+                    if (r) {
+                        l = r[0].length;
+                        r1 = r[1];
+                        c = r1.charAt(0);
+                        s = s.substr(l);
+                        from = character + l - r1.length;
+                        character += l;
+                        return r1;
+                    }
+                }
+
+                function string(x) {
+                    var c, j, r = '', allowNewLine = false;
+
+                    if (jsonmode && x !== '"') {
+                        warningAt("Strings must use doublequote.",
+                                line, character);
+                    }
+
+                    function esc(n) {
+                        var i = parseInt(s.substr(j + 1, n), 16);
+                        j += n;
+                        if (i >= 32 && i <= 126 &&
+                                i !== 34 && i !== 92 && i !== 39) {
+                            warningAt("Unnecessary escapement.", line, character);
+                        }
+                        character += n;
+                        c = String.fromCharCode(i);
+                    }
+                    j = 0;
+unclosedString:     for (;;) {
+                        while (j >= s.length) {
+                            j = 0;
+
+                            var cl = line, cf = from;
+                            if (!nextLine()) {
+                                errorAt("Unclosed string.", cl, cf);
+                                break unclosedString;
+                            }
+
+                            if (allowNewLine) {
+                                allowNewLine = false;
+                            } else {
+                                warningAt("Unclosed string.", cl, cf);
+                            }
+                        }
+                        c = s.charAt(j);
+                        if (c === x) {
+                            character += 1;
+                            s = s.substr(j + 1);
+                            return it('(string)', r, x);
+                        }
+                        if (c < ' ') {
+                            if (c === '\n' || c === '\r') {
+                                break;
+                            }
+                            warningAt("Control character in string: {a}.",
+                                    line, character + j, s.slice(0, j));
+                        } else if (c === '\\') {
+                            j += 1;
+                            character += 1;
+                            c = s.charAt(j);
+                            switch (c) {
+                            case '\\':
+                            case '"':
+                            case '/':
+                                break;
+                            case '\'':
+                                if (jsonmode) {
+                                    warningAt("Avoid \\'.", line, character);
+                                }
+                                break;
+                            case 'b':
+                                c = '\b';
+                                break;
+                            case 'f':
+                                c = '\f';
+                                break;
+                            case 'n':
+                                c = '\n';
+                                break;
+                            case 'r':
+                                c = '\r';
+                                break;
+                            case 't':
+                                c = '\t';
+                                break;
+                            case 'u':
+                                esc(4);
+                                break;
+                            case 'v':
+                                if (jsonmode) {
+                                    warningAt("Avoid \\v.", line, character);
+                                }
+                                c = '\v';
+                                break;
+                            case 'x':
+                                if (jsonmode) {
+                                    warningAt("Avoid \\x-.", line, character);
+                                }
+                                esc(2);
+                                break;
+                            case '':
+                                // last character is escape character
+                                // always allow new line if escaped, but show
+                                // warning if option is not set
+                                allowNewLine = true;
+                                if (option.multistr) {
+                                    if (jsonmode) {
+                                        warningAt("Avoid EOL escapement.", line, character);
+                                    }
+                                    c = '';
+                                    character -= 1;
+                                    break;
+                                }
+                                warningAt("Bad escapement of EOL. Use option multistr if needed.",
+                                    line, character);
+                                break;
+                            default:
+                                warningAt("Bad escapement.", line, character);
+                            }
+                        }
+                        r += c;
+                        character += 1;
+                        j += 1;
+                    }
+                }
+
+                for (;;) {
+                    if (!s) {
+                        return it(nextLine() ? '(endline)' : '(end)', '');
+                    }
+                    t = match(tx);
+                    if (!t) {
+                        t = '';
+                        c = '';
+                        while (s && s < '!') {
+                            s = s.substr(1);
+                        }
+                        if (s) {
+                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));
+                            s = '';
+                        }
+                    } else {
+
+    //      identifier
+
+                        if (c.isAlpha() || c === '_' || c === '$') {
+                            return it('(identifier)', t);
+                        }
+
+    //      number
+
+                        if (c.isDigit()) {
+                            if (!isFinite(Number(t))) {
+                                warningAt("Bad number '{a}'.",
+                                    line, character, t);
+                            }
+                            if (s.substr(0, 1).isAlpha()) {
+                                warningAt("Missing space after '{a}'.",
+                                        line, character, t);
+                            }
+                            if (c === '0') {
+                                d = t.substr(1, 1);
+                                if (d.isDigit()) {
+                                    if (token.id !== '.') {
+                                        warningAt("Don't use extra leading zeros '{a}'.",
+                                            line, character, t);
+                                    }
+                                } else if (jsonmode && (d === 'x' || d === 'X')) {
+                                    warningAt("Avoid 0x-. '{a}'.",
+                                            line, character, t);
+                                }
+                            }
+                            if (t.substr(t.length - 1) === '.') {
+                                warningAt(
+"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);
+                            }
+                            return it('(number)', t);
+                        }
+                        switch (t) {
+
+    //      string
+
+                        case '"':
+                        case "'":
+                            return string(t);
+
+    //      // comment
+
+                        case '//':
+                            s = '';
+                            token.comment = true;
+                            break;
+
+    //      /* comment
+
+                        case '/*':
+                            for (;;) {
+                                i = s.search(lx);
+                                if (i >= 0) {
+                                    break;
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed comment.", line, character);
+                                }
+                            }
+                            character += i + 2;
+                            if (s.substr(i, 1) === '/') {
+                                errorAt("Nested comment.", line, character);
+                            }
+                            s = s.substr(i + 2);
+                            token.comment = true;
+                            break;
+
+    //      /*members /*jshint /*global
+
+                        case '/*members':
+                        case '/*member':
+                        case '/*jshint':
+                        case '/*jslint':
+                        case '/*global':
+                        case '*/':
+                            return {
+                                value: t,
+                                type: 'special',
+                                line: line,
+                                character: character,
+                                from: from
+                            };
+
+                        case '':
+                            break;
+    //      /
+                        case '/':
+                            if (token.id === '/=') {
+                                errorAt("A regular expression literal can be confused with '/='.",
+                                    line, from);
+                            }
+                            if (prereg) {
+                                depth = 0;
+                                captures = 0;
+                                l = 0;
+                                for (;;) {
+                                    b = true;
+                                    c = s.charAt(l);
+                                    l += 1;
+                                    switch (c) {
+                                    case '':
+                                        errorAt("Unclosed regular expression.", line, from);
+                                        return quit('Stopping.', line, from);
+                                    case '/':
+                                        if (depth > 0) {
+                                            warningAt("{a} unterminated regular expression " +
+                                                "group(s).", line, from + l, depth);
+                                        }
+                                        c = s.substr(0, l - 1);
+                                        q = {
+                                            g: true,
+                                            i: true,
+                                            m: true
+                                        };
+                                        while (q[s.charAt(l)] === true) {
+                                            q[s.charAt(l)] = false;
+                                            l += 1;
+                                        }
+                                        character += l;
+                                        s = s.substr(l);
+                                        q = s.charAt(0);
+                                        if (q === '/' || q === '*') {
+                                            errorAt("Confusing regular expression.",
+                                                    line, from);
+                                        }
+                                        return it('(regexp)', c);
+                                    case '\\':
+                                        c = s.charAt(l);
+                                        if (c < ' ') {
+                                            warningAt(
+"Unexpected control character in regular expression.", line, from + l);
+                                        } else if (c === '<') {
+                                            warningAt(
+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                        }
+                                        l += 1;
+                                        break;
+                                    case '(':
+                                        depth += 1;
+                                        b = false;
+                                        if (s.charAt(l) === '?') {
+                                            l += 1;
+                                            switch (s.charAt(l)) {
+                                            case ':':
+                                            case '=':
+                                            case '!':
+                                                l += 1;
+                                                break;
+                                            default:
+                                                warningAt(
+"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
+                                            }
+                                        } else {
+                                            captures += 1;
+                                        }
+                                        break;
+                                    case '|':
+                                        b = false;
+                                        break;
+                                    case ')':
+                                        if (depth === 0) {
+                                            warningAt("Unescaped '{a}'.",
+                                                    line, from + l, ')');
+                                        } else {
+                                            depth -= 1;
+                                        }
+                                        break;
+                                    case ' ':
+                                        q = 1;
+                                        while (s.charAt(l) === ' ') {
+                                            l += 1;
+                                            q += 1;
+                                        }
+                                        if (q > 1) {
+                                            warningAt(
+"Spaces are hard to count. Use {{a}}.", line, from + l, q);
+                                        }
+                                        break;
+                                    case '[':
+                                        c = s.charAt(l);
+                                        if (c === '^') {
+                                            l += 1;
+                                            if (option.regexp) {
+                                                warningAt("Insecure '{a}'.",
+                                                        line, from + l, c);
+                                            } else if (s.charAt(l) === ']') {
+                                                errorAt("Unescaped '{a}'.",
+                                                    line, from + l, '^');
+                                            }
+                                        }
+                                        if (c === ']') {
+                                            warningAt("Empty class.", line,
+                                                    from + l - 1);
+                                        }
+                                        isLiteral = false;
+                                        isInRange = false;
+klass:                                  do {
+                                            c = s.charAt(l);
+                                            l += 1;
+                                            switch (c) {
+                                            case '[':
+                                            case '^':
+                                                warningAt("Unescaped '{a}'.",
+                                                        line, from + l, c);
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case '-':
+                                                if (isLiteral && !isInRange) {
+                                                    isLiteral = false;
+                                                    isInRange = true;
+                                                } else if (isInRange) {
+                                                    isInRange = false;
+                                                } else if (s.charAt(l) === ']') {
+                                                    isInRange = true;
+                                                } else {
+                                                    if (option.regexdash !== (l === 2 || (l === 3 &&
+                                                        s.charAt(1) === '^'))) {
+                                                        warningAt("Unescaped '{a}'.",
+                                                            line, from + l - 1, '-');
+                                                    }
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case ']':
+                                                if (isInRange && !option.regexdash) {
+                                                    warningAt("Unescaped '{a}'.",
+                                                            line, from + l - 1, '-');
+                                                }
+                                                break klass;
+                                            case '\\':
+                                                c = s.charAt(l);
+                                                if (c < ' ') {
+                                                    warningAt(
+"Unexpected control character in regular expression.", line, from + l);
+                                                } else if (c === '<') {
+                                                    warningAt(
+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                                }
+                                                l += 1;
+
+                                                // \w, \s and \d are never part of a character range
+                                                if (/[wsd]/i.test(c)) {
+                                                    if (isInRange) {
+                                                        warningAt("Unescaped '{a}'.",
+                                                            line, from + l, '-');
+                                                        isInRange = false;
+                                                    }
+                                                    isLiteral = false;
+                                                } else if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case '/':
+                                                warningAt("Unescaped '{a}'.",
+                                                        line, from + l - 1, '/');
+
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            case '<':
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                                break;
+                                            default:
+                                                if (isInRange) {
+                                                    isInRange = false;
+                                                } else {
+                                                    isLiteral = true;
+                                                }
+                                            }
+                                        } while (c);
+                                        break;
+                                    case '.':
+                                        if (option.regexp) {
+                                            warningAt("Insecure '{a}'.", line,
+                                                    from + l, c);
+                                        }
+                                        break;
+                                    case ']':
+                                    case '?':
+                                    case '{':
+                                    case '}':
+                                    case '+':
+                                    case '*':
+                                        warningAt("Unescaped '{a}'.", line,
+                                                from + l, c);
+                                    }
+                                    if (b) {
+                                        switch (s.charAt(l)) {
+                                        case '?':
+                                        case '+':
+                                        case '*':
+                                            l += 1;
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            break;
+                                        case '{':
+                                            l += 1;
+                                            c = s.charAt(l);
+                                            if (c < '0' || c > '9') {
+                                                warningAt(
+"Expected a number and instead saw '{a}'.", line, from + l, c);
+                                            }
+                                            l += 1;
+                                            low = +c;
+                                            for (;;) {
+                                                c = s.charAt(l);
+                                                if (c < '0' || c > '9') {
+                                                    break;
+                                                }
+                                                l += 1;
+                                                low = +c + (low * 10);
+                                            }
+                                            high = low;
+                                            if (c === ',') {
+                                                l += 1;
+                                                high = Infinity;
+                                                c = s.charAt(l);
+                                                if (c >= '0' && c <= '9') {
+                                                    l += 1;
+                                                    high = +c;
+                                                    for (;;) {
+                                                        c = s.charAt(l);
+                                                        if (c < '0' || c > '9') {
+                                                            break;
+                                                        }
+                                                        l += 1;
+                                                        high = +c + (high * 10);
+                                                    }
+                                                }
+                                            }
+                                            if (s.charAt(l) !== '}') {
+                                                warningAt(
+"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
+                                            } else {
+                                                l += 1;
+                                            }
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            if (low > high) {
+                                                warningAt(
+"'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+                                            }
+                                        }
+                                    }
+                                }
+                                c = s.substr(0, l - 1);
+                                character += l;
+                                s = s.substr(l);
+                                return it('(regexp)', c);
+                            }
+                            return it('(punctuator)', t);
+
+    //      punctuator
+
+                        case '#':
+                            return it('(punctuator)', t);
+                        default:
+                            return it('(punctuator)', t);
+                        }
+                    }
+                }
+            }
+        };
+    }());
+
+
+    function addlabel(t, type) {
+
+        if (t === 'hasOwnProperty') {
+            warning("'hasOwnProperty' is a really bad name.");
+        }
+
+// Define t in the current function in the current scope.
+        if (is_own(funct, t) && !funct['(global)']) {
+            if (funct[t] === true) {
+                if (option.latedef)
+                    warning("'{a}' was used before it was defined.", nexttoken, t);
+            } else {
+                if (!option.shadow && type !== "exception")
+                    warning("'{a}' is already defined.", nexttoken, t);
+            }
+        }
+
+        funct[t] = type;
+        if (funct['(global)']) {
+            global[t] = funct;
+            if (is_own(implied, t)) {
+                if (option.latedef)
+                    warning("'{a}' was used before it was defined.", nexttoken, t);
+                delete implied[t];
+            }
+        } else {
+            scope[t] = funct;
+        }
+    }
+
+
+    function doOption() {
+        var b, obj, filter, o = nexttoken.value, t, v;
+        switch (o) {
+        case '*/':
+            error("Unbegun comment.");
+            break;
+        case '/*members':
+        case '/*member':
+            o = '/*members';
+            if (!membersOnly) {
+                membersOnly = {};
+            }
+            obj = membersOnly;
+            break;
+        case '/*jshint':
+        case '/*jslint':
+            obj = option;
+            filter = boolOptions;
+            break;
+        case '/*global':
+            obj = predefined;
+            break;
+        default:
+            error("What?");
+        }
+        t = lex.token();
+loop:   for (;;) {
+            for (;;) {
+                if (t.type === 'special' && t.value === '*/') {
+                    break loop;
+                }
+                if (t.id !== '(endline)' && t.id !== ',') {
+                    break;
+                }
+                t = lex.token();
+            }
+            if (t.type !== '(string)' && t.type !== '(identifier)' &&
+                    o !== '/*members') {
+                error("Bad option.", t);
+            }
+            v = lex.token();
+            if (v.id === ':') {
+                v = lex.token();
+                if (obj === membersOnly) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, '*/', ':');
+                }
+                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.white = true;
+                    obj.indent = b;
+                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxerr = b;
+                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxlen = b;
+                } else if (t.value === 'validthis') {
+                    if (funct['(global)']) {
+                        error("Option 'validthis' can't be used in a global scope.");
+                    } else {
+                        if (v.value === 'true' || v.value === 'false')
+                            obj[t.value] = v.value === 'true';
+                        else
+                            error("Bad option value.", v);
+                    }
+                } else if (v.value === 'true') {
+                    obj[t.value] = true;
+                } else if (v.value === 'false') {
+                    obj[t.value] = false;
+                } else {
+                    error("Bad option value.", v);
+                }
+                t = lex.token();
+            } else {
+                if (o === '/*jshint' || o === '/*jslint') {
+                    error("Missing option value.", t);
+                }
+                obj[t.value] = false;
+                t = v;
+            }
+        }
+        if (filter) {
+            assume();
+        }
+    }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+//     for ( var i in ...
+// from
+//     for ( var i = ...
+
+    function peek(p) {
+        var i = p || 0, j = 0, t;
+
+        while (j <= i) {
+            t = lookahead[j];
+            if (!t) {
+                t = lookahead[j] = lex.token();
+            }
+            j += 1;
+        }
+        return t;
+    }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+    function advance(id, t) {
+        switch (token.id) {
+        case '(number)':
+            if (nexttoken.id === '.') {
+                warning("A dot following a number can be confused with a decimal point.", token);
+            }
+            break;
+        case '-':
+            if (nexttoken.id === '-' || nexttoken.id === '--') {
+                warning("Confusing minusses.");
+            }
+            break;
+        case '+':
+            if (nexttoken.id === '+' || nexttoken.id === '++') {
+                warning("Confusing plusses.");
+            }
+            break;
+        }
+
+        if (token.type === '(string)' || token.identifier) {
+            anonname = token.value;
+        }
+
+        if (id && nexttoken.id !== id) {
+            if (t) {
+                if (nexttoken.id === '(end)') {
+                    warning("Unmatched '{a}'.", t, t.id);
+                } else {
+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+                            nexttoken, id, t.id, t.line, nexttoken.value);
+                }
+            } else if (nexttoken.type !== '(identifier)' ||
+                            nexttoken.value !== id) {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, id, nexttoken.value);
+            }
+        }
+
+        prevtoken = token;
+        token = nexttoken;
+        for (;;) {
+            nexttoken = lookahead.shift() || lex.token();
+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                return;
+            }
+            if (nexttoken.type === 'special') {
+                doOption();
+            } else {
+                if (nexttoken.id !== '(endline)') {
+                    break;
+                }
+            }
+        }
+    }
+
+
+// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
+// like .nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define statement-oriented languages like
+// JavaScript. I retained Pratt's nomenclature.
+
+// .nud     Null denotation
+// .fud     First null denotation
+// .led     Left denotation
+//  lbp     Left binding power
+//  rbp     Right binding power
+
+// They are elements of the parsing method called Top Down Operator Precedence.
+
+    function expression(rbp, initial) {
+        var left, isArray = false;
+
+        if (nexttoken.id === '(end)')
+            error("Unexpected early end of program.", token);
+
+        advance();
+        if (initial) {
+            anonname = 'anonymous';
+            funct['(verb)'] = token.value;
+        }
+        if (initial === true && token.fud) {
+            left = token.fud();
+        } else {
+            if (token.nud) {
+                left = token.nud();
+            } else {
+                if (nexttoken.type === '(number)' && token.id === '.') {
+                    warning("A leading decimal point can be confused with a dot: '.{a}'.",
+                            token, nexttoken.value);
+                    advance();
+                    return token;
+                } else {
+                    error("Expected an identifier and instead saw '{a}'.",
+                            token, token.id);
+                }
+            }
+            while (rbp < nexttoken.lbp) {
+                isArray = token.value === 'Array';
+                advance();
+                if (isArray && token.id === '(' && nexttoken.id === ')')
+                    warning("Use the array literal notation [].", token);
+                if (token.led) {
+                    left = token.led(left);
+                } else {
+                    error("Expected an operator and instead saw '{a}'.",
+                        token, token.id);
+                }
+            }
+        }
+        return left;
+    }
+
+
+// Functions for conformance of style.
+
+    function adjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white) {
+            if (left.character !== right.from && left.line === right.line) {
+                left.from += (left.character - left.from);
+                warning("Unexpected space after '{a}'.", left, left.value);
+            }
+        }
+    }
+
+    function nobreak(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && (left.character !== right.from || left.line !== right.line)) {
+            warning("Unexpected space before '{a}'.", right, right.value);
+        }
+    }
+
+    function nospace(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && !left.comment) {
+            if (left.line === right.line) {
+                adjacent(left, right);
+            }
+        }
+    }
+
+    function nonadjacent(left, right) {
+        if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.line === right.line && left.character === right.from) {
+                left.from += (left.character - left.from);
+                warning("Missing space after '{a}'.",
+                        left, left.value);
+            }
+        }
+    }
+
+    function nobreaknonadjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (!option.laxbreak && left.line !== right.line) {
+            warning("Bad line breaking before '{a}'.", right, right.id);
+        } else if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.character === right.from) {
+                left.from += (left.character - left.from);
+                warning("Missing space after '{a}'.",
+                        left, left.value);
+            }
+        }
+    }
+
+    function indentation(bias) {
+        var i;
+        if (option.white && nexttoken.id !== '(end)') {
+            i = indent + (bias || 0);
+            if (nexttoken.from !== i) {
+                warning(
+"Expected '{a}' to have an indentation at {b} instead at {c}.",
+                        nexttoken, nexttoken.value, i, nexttoken.from);
+            }
+        }
+    }
+
+    function nolinebreak(t) {
+        t = t || token;
+        if (t.line !== nexttoken.line) {
+            warning("Line breaking error '{a}'.", t, t.value);
+        }
+    }
+
+
+    function comma() {
+        if (token.line !== nexttoken.line) {
+            if (!option.laxbreak) {
+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
+            }
+        } else if (!token.comment && token.character !== nexttoken.from && option.white) {
+            token.from += (token.character - token.from);
+            warning("Unexpected space after '{a}'.", token, token.value);
+        }
+        advance(',');
+        nonadjacent(token, nexttoken);
+    }
+
+
+// Functional constructors for making the symbols that will be inherited by
+// tokens.
+
+    function symbol(s, p) {
+        var x = syntax[s];
+        if (!x || typeof x !== 'object') {
+            syntax[s] = x = {
+                id: s,
+                lbp: p,
+                value: s
+            };
+        }
+        return x;
+    }
+
+
+    function delim(s) {
+        return symbol(s, 0);
+    }
+
+
+    function stmt(s, f) {
+        var x = delim(s);
+        x.identifier = x.reserved = true;
+        x.fud = f;
+        return x;
+    }
+
+
+    function blockstmt(s, f) {
+        var x = stmt(s, f);
+        x.block = true;
+        return x;
+    }
+
+
+    function reserveName(x) {
+        var c = x.id.charAt(0);
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+            x.identifier = x.reserved = true;
+        }
+        return x;
+    }
+
+
+    function prefix(s, f) {
+        var x = symbol(s, 150);
+        reserveName(x);
+        x.nud = (typeof f === 'function') ? f : function () {
+            this.right = expression(150);
+            this.arity = 'unary';
+            if (this.id === '++' || this.id === '--') {
+                if (option.plusplus) {
+                    warning("Unexpected use of '{a}'.", this, this.id);
+                } else if ((!this.right.identifier || this.right.reserved) &&
+                        this.right.id !== '.' && this.right.id !== '[') {
+                    warning("Bad operand.", this);
+                }
+            }
+            return this;
+        };
+        return x;
+    }
+
+
+    function type(s, f) {
+        var x = delim(s);
+        x.type = s;
+        x.nud = f;
+        return x;
+    }
+
+
+    function reserve(s, f) {
+        var x = type(s, f);
+        x.identifier = x.reserved = true;
+        return x;
+    }
+
+
+    function reservevar(s, v) {
+        return reserve(s, function () {
+            if (typeof v === 'function') {
+                v(this);
+            }
+            return this;
+        });
+    }
+
+
+    function infix(s, f, p, w) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = function (left) {
+            if (!w) {
+                nobreaknonadjacent(prevtoken, token);
+                nonadjacent(token, nexttoken);
+            }
+            if (s === "in" && left.id === "!") {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (typeof f === 'function') {
+                return f(left, this);
+            } else {
+                this.left = left;
+                this.right = expression(p);
+                return this;
+            }
+        };
+        return x;
+    }
+
+
+    function relation(s, f) {
+        var x = symbol(s, 100);
+        x.led = function (left) {
+            nobreaknonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            var right = expression(100);
+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
+                warning("Use the isNaN function to compare with NaN.", this);
+            } else if (f) {
+                f.apply(this, [left, right]);
+            }
+            if (left.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (right.id === '!') {
+                warning("Confusing use of '{a}'.", right, '!');
+            }
+            this.left = left;
+            this.right = right;
+            return this;
+        };
+        return x;
+    }
+
+
+    function isPoorRelation(node) {
+        return node &&
+              ((node.type === '(number)' && +node.value === 0) ||
+               (node.type === '(string)' && node.value === '') ||
+               (node.type === 'null' && !option.eqnull) ||
+                node.type === 'true' ||
+                node.type === 'false' ||
+                node.type === 'undefined');
+    }
+
+
+    function assignop(s, f) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            var l;
+            that.left = left;
+            if (predefined[left.value] === false &&
+                    scope[left.value]['(global)'] === true) {
+                warning("Read only.", left);
+            } else if (left['function']) {
+                warning("'{a}' is a function.", left, left.value);
+            }
+            if (left) {
+                if (option.esnext && funct[left.value] === 'const') {
+                    warning("Attempting to override '{a}' which is a constant", left, left.value);
+                }
+                if (left.id === '.' || left.id === '[') {
+                    if (!left.left || left.left.value === 'arguments') {
+                        warning('Bad assignment.', that);
+                    }
+                    that.right = expression(19);
+                    return that;
+                } else if (left.identifier && !left.reserved) {
+                    if (funct[left.value] === 'exception') {
+                        warning("Do not assign to the exception parameter.", left);
+                    }
+                    that.right = expression(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function bitwise(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.left = left;
+            this.right = expression(p);
+            return this;
+        };
+        return x;
+    }
+
+
+    function bitwiseassignop(s) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", that, that.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (left) {
+                if (left.id === '.' || left.id === '[' ||
+                        (left.identifier && !left.reserved)) {
+                    expression(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+                                token);
+                }
+                return that;
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function suffix(s, f) {
+        var x = symbol(s, 150);
+        x.led = function (left) {
+            if (option.plusplus) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            } else if ((!left.identifier || left.reserved) &&
+                    left.id !== '.' && left.id !== '[') {
+                warning("Bad operand.", this);
+            }
+            this.left = left;
+            return this;
+        };
+        return x;
+    }
+
+
+    // fnparam means that this identifier is being defined as a function
+    // argument (see identifier())
+    function optionalidentifier(fnparam) {
+        if (nexttoken.identifier) {
+            advance();
+            if (token.reserved && !option.es5) {
+                // `undefined` as a function param is a common pattern to protect
+                // against the case when somebody does `undefined = true` and
+                // help with minification. More info: https://gist.github.com/315916
+                if (!fnparam || token.value !== 'undefined') {
+                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+                            token, token.id);
+                }
+            }
+            return token.value;
+        }
+    }
+
+    // fnparam means that this identifier is being defined as a function
+    // argument
+    function identifier(fnparam) {
+        var i = optionalidentifier(fnparam);
+        if (i) {
+            return i;
+        }
+        if (token.id === 'function' && nexttoken.id === '(') {
+            warning("Missing name in function declaration.");
+        } else {
+            error("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    }
+
+
+    function reachable(s) {
+        var i = 0, t;
+        if (nexttoken.id !== ';' || noreach) {
+            return;
+        }
+        for (;;) {
+            t = peek(i);
+            if (t.reach) {
+                return;
+            }
+            if (t.id !== '(endline)') {
+                if (t.id === 'function') {
+                    warning(
+"Inner functions should be listed at the top of the outer function.", t);
+                    break;
+                }
+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+                break;
+            }
+            i += 1;
+        }
+    }
+
+
+    function statement(noindent) {
+        var i = indent, r, s = scope, t = nexttoken;
+
+        if (t.id === ";") {
+            advance(";");
+            return;
+        }
+
+// Is this a labelled statement?
+
+        if (t.identifier && !t.reserved && peek().id === ':') {
+            advance();
+            advance(':');
+            scope = Object.create(s);
+            addlabel(t.value, 'label');
+            if (!nexttoken.labelled) {
+                warning("Label '{a}' on {b} statement.",
+                        nexttoken, t.value, nexttoken.value);
+            }
+            if (jx.test(t.value + ':')) {
+                warning("Label '{a}' looks like a javascript url.",
+                        t, t.value);
+            }
+            nexttoken.label = t.value;
+            t = nexttoken;
+        }
+
+// Parse the statement.
+
+        if (!noindent) {
+            indentation();
+        }
+        r = expression(0, true);
+
+        // Look for the final semicolon.
+        if (!t.block) {
+            if (!option.expr && (!r || !r.exps)) {
+                warning("Expected an assignment or function call and instead saw an expression.",
+                    token);
+            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {
+                warning("Do not use 'new' for side effects.");
+            }
+
+            if (nexttoken.id !== ';') {
+                if (!option.asi) {
+                    // If this is the last statement in a block that ends on
+                    // the same line *and* option lastsemic is on, ignore the warning.
+                    // Otherwise, complain about missing semicolon.
+                    if (!option.lastsemic || nexttoken.id !== '}' ||
+                            nexttoken.line !== token.line) {
+                        warningAt("Missing semicolon.", token.line, token.character);
+                    }
+                }
+            } else {
+                adjacent(token, nexttoken);
+                advance(';');
+                nonadjacent(token, nexttoken);
+            }
+        }
+
+// Restore the indentation.
+
+        indent = i;
+        scope = s;
+        return r;
+    }
+
+
+    function statements(startLine) {
+        var a = [], f, p;
+
+        while (!nexttoken.reach && nexttoken.id !== '(end)') {
+            if (nexttoken.id === ';') {
+                warning("Unnecessary semicolon.");
+                advance(';');
+            } else {
+                a.push(statement(startLine === nexttoken.line));
+            }
+        }
+        return a;
+    }
+
+
+    /*
+     * read all directives
+     * recognizes a simple form of asi, but always
+     * warns, if it is used
+     */
+    function directives() {
+        var i, p, pn;
+
+        for (;;) {
+            if (nexttoken.id === "(string)") {
+                p = peek(0);
+                if (p.id === "(endline)") {
+                    i = 1;
+                    do {
+                        pn = peek(i);
+                        i = i + 1;
+                    } while (pn.id === "(endline)");
+
+                    if (pn.id !== ";") {
+                        if (pn.id !== "(string)" && pn.id !== "(number)" &&
+                            pn.id !== "(regexp)" && pn.identifier !== true &&
+                            pn.id !== "}") {
+                            break;
+                        }
+                        warning("Missing semicolon.", nexttoken);
+                    } else {
+                        p = pn;
+                    }
+                } else if (p.id === "}") {
+                    // directive with no other statements, warn about missing semicolon
+                    warning("Missing semicolon.", p);
+                } else if (p.id !== ";") {
+                    break;
+                }
+
+                indentation();
+                advance();
+                if (directive[token.value]) {
+                    warning("Unnecessary directive \"{a}\".", token, token.value);
+                }
+
+                if (token.value === "use strict") {
+                    option.newcap = true;
+                    option.undef = true;
+                }
+
+                // there's no directive negation, so always set to true
+                directive[token.value] = true;
+
+                if (p.id === ";") {
+                    advance(";");
+                }
+                continue;
+            }
+            break;
+        }
+    }
+
+
+    /*
+     * Parses a single block. A block is a sequence of statements wrapped in
+     * braces.
+     *
+     * ordinary - true for everything but function bodies and try blocks.
+     * stmt     - true if block can be a single statement (e.g. in if/for/while).
+     * isfunc   - true if block is a function body
+     */
+    function block(ordinary, stmt, isfunc) {
+        var a,
+            b = inblock,
+            old_indent = indent,
+            m,
+            s = scope,
+            t,
+            line,
+            d;
+
+        inblock = ordinary;
+        if (!ordinary || !option.funcscope) scope = Object.create(scope);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+
+        if (nexttoken.id === '{') {
+            advance('{');
+            line = token.line;
+            if (nexttoken.id !== '}') {
+                indent += option.indent;
+                while (!ordinary && nexttoken.from > indent) {
+                    indent += option.indent;
+                }
+
+                if (isfunc) {
+                    m = {};
+                    for (d in directive) {
+                        if (is_own(directive, d)) {
+                            m[d] = directive[d];
+                        }
+                    }
+                    directives();
+
+                    if (option.strict && funct['(context)']['(global)']) {
+                        if (!m["use strict"] && !directive["use strict"]) {
+                            warning("Missing \"use strict\" statement.");
+                        }
+                    }
+                }
+
+                a = statements(line);
+
+                if (isfunc) {
+                    directive = m;
+                }
+
+                indent -= option.indent;
+                if (line !== nexttoken.line) {
+                    indentation();
+                }
+            } else if (line !== nexttoken.line) {
+                indentation();
+            }
+            advance('}', t);
+            indent = old_indent;
+        } else if (!ordinary) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                  nexttoken, '{', nexttoken.value);
+        } else {
+            if (!stmt || option.curly)
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, '{', nexttoken.value);
+
+            noreach = true;
+            indent += option.indent;
+            // test indentation only if statement is in new line
+            a = [statement(nexttoken.line === token.line)];
+            indent -= option.indent;
+            noreach = false;
+        }
+        funct['(verb)'] = null;
+        if (!ordinary || !option.funcscope) scope = s;
+        inblock = b;
+        if (ordinary && option.noempty && (!a || a.length === 0)) {
+            warning("Empty block.");
+        }
+        return a;
+    }
+
+
+    function countMember(m) {
+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
+            warning("Unexpected /*member '{a}'.", token, m);
+        }
+        if (typeof member[m] === 'number') {
+            member[m] += 1;
+        } else {
+            member[m] = 1;
+        }
+    }
+
+
+    function note_implied(token) {
+        var name = token.value, line = token.line, a = implied[name];
+        if (typeof a === 'function') {
+            a = false;
+        }
+        if (!a) {
+            a = [line];
+            implied[name] = a;
+        } else if (a[a.length - 1] !== line) {
+            a.push(line);
+        }
+    }
+
+
+    // Build the syntax table by declaring the syntactic elements of the language.
+
+    type('(number)', function () {
+        return this;
+    });
+
+    type('(string)', function () {
+        return this;
+    });
+
+    syntax['(identifier)'] = {
+        type: '(identifier)',
+        lbp: 0,
+        identifier: true,
+        nud: function () {
+            var v = this.value,
+                s = scope[v],
+                f;
+
+            if (typeof s === 'function') {
+                // Protection against accidental inheritance.
+                s = undefined;
+            } else if (typeof s === 'boolean') {
+                f = funct;
+                funct = functions[0];
+                addlabel(v, 'var');
+                s = funct;
+                funct = f;
+            }
+
+            // The name is in scope and defined in the current function.
+            if (funct === s) {
+                // Change 'unused' to 'var', and reject labels.
+                switch (funct[v]) {
+                case 'unused':
+                    funct[v] = 'var';
+                    break;
+                case 'unction':
+                    funct[v] = 'function';
+                    this['function'] = true;
+                    break;
+                case 'function':
+                    this['function'] = true;
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                }
+            } else if (funct['(global)']) {
+                // The name is not defined in the function.  If we are in the global
+                // scope, then we have an undefined variable.
+                //
+                // Operators typeof and delete do not raise runtime errors even if
+                // the base object of a reference is null so no need to display warning
+                // if we're inside of typeof or delete.
+                if (anonname !== 'typeof' && anonname !== 'delete' &&
+                    option.undef && typeof predefined[v] !== 'boolean') {
+                    isundef(funct, "'{a}' is not defined.", token, v);
+                }
+                note_implied(token);
+            } else {
+                // If the name is already defined in the current
+                // function, but not as outer, then there is a scope error.
+
+                switch (funct[v]) {
+                case 'closure':
+                case 'function':
+                case 'var':
+                case 'unused':
+                    warning("'{a}' used out of scope.", token, v);
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                case 'outer':
+                case 'global':
+                    break;
+                default:
+                    // If the name is defined in an outer function, make an outer entry,
+                    // and if it was unused, make it var.
+                    if (s === true) {
+                        funct[v] = true;
+                    } else if (s === null) {
+                        warning("'{a}' is not allowed.", token, v);
+                        note_implied(token);
+                    } else if (typeof s !== 'object') {
+                        // Operators typeof and delete do not raise runtime errors even
+                        // if the base object of a reference is null so no need to
+                        // display warning if we're inside of typeof or delete.
+                        if (anonname !== 'typeof' && anonname !== 'delete' && option.undef) {
+                            isundef(funct, "'{a}' is not defined.", token, v);
+                        }
+                        funct[v] = true;
+                        note_implied(token);
+                    } else {
+                        switch (s[v]) {
+                        case 'function':
+                        case 'unction':
+                            this['function'] = true;
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'var':
+                        case 'unused':
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'closure':
+                        case 'parameter':
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'label':
+                            warning("'{a}' is a statement label.", token, v);
+                        }
+                    }
+                }
+            }
+            return this;
+        },
+        led: function () {
+            error("Expected an operator and instead saw '{a}'.",
+                nexttoken, nexttoken.value);
+        }
+    };
+
+    type('(regexp)', function () {
+        return this;
+    });
+
+
+// ECMAScript parser
+
+    delim('(endline)');
+    delim('(begin)');
+    delim('(end)').reach = true;
+    delim('</').reach = true;
+    delim('<!');
+    delim('<!--');
+    delim('-->');
+    delim('(error)').reach = true;
+    delim('}').reach = true;
+    delim(')');
+    delim(']');
+    delim('"').reach = true;
+    delim("'").reach = true;
+    delim(';');
+    delim(':').reach = true;
+    delim(',');
+    delim('#');
+    delim('@');
+    reserve('else');
+    reserve('case').reach = true;
+    reserve('catch');
+    reserve('default').reach = true;
+    reserve('finally');
+    reservevar('arguments', function (x) {
+        if (directive['use strict'] && funct['(global)']) {
+            warning("Strict violation.", x);
+        }
+    });
+    reservevar('eval');
+    reservevar('false');
+    reservevar('Infinity');
+    reservevar('NaN');
+    reservevar('null');
+    reservevar('this', function (x) {
+        if (directive['use strict'] && !option.validthis && ((funct['(statement)'] &&
+                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {
+            warning("Possible strict violation.", x);
+        }
+    });
+    reservevar('true');
+    reservevar('undefined');
+    assignop('=', 'assign', 20);
+    assignop('+=', 'assignadd', 20);
+    assignop('-=', 'assignsub', 20);
+    assignop('*=', 'assignmult', 20);
+    assignop('/=', 'assigndiv', 20).nud = function () {
+        error("A regular expression literal can be confused with '/='.");
+    };
+    assignop('%=', 'assignmod', 20);
+    bitwiseassignop('&=', 'assignbitand', 20);
+    bitwiseassignop('|=', 'assignbitor', 20);
+    bitwiseassignop('^=', 'assignbitxor', 20);
+    bitwiseassignop('<<=', 'assignshiftleft', 20);
+    bitwiseassignop('>>=', 'assignshiftright', 20);
+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
+    infix('?', function (left, that) {
+        that.left = left;
+        that.right = expression(10);
+        advance(':');
+        that['else'] = expression(10);
+        return that;
+    }, 30);
+
+    infix('||', 'or', 40);
+    infix('&&', 'and', 50);
+    bitwise('|', 'bitor', 70);
+    bitwise('^', 'bitxor', 80);
+    bitwise('&', 'bitand', 90);
+    relation('==', function (left, right) {
+        var eqnull = option.eqnull && (left.value === 'null' || right.value === 'null');
+
+        if (!eqnull && option.eqeqeq)
+            warning("Expected '{a}' and instead saw '{b}'.", this, '===', '==');
+        else if (isPoorRelation(left))
+            warning("Use '{a}' to compare with '{b}'.", this, '===', left.value);
+        else if (isPoorRelation(right))
+            warning("Use '{a}' to compare with '{b}'.", this, '===', right.value);
+
+        return this;
+    });
+    relation('===');
+    relation('!=', function (left, right) {
+        var eqnull = option.eqnull &&
+                (left.value === 'null' || right.value === 'null');
+
+        if (!eqnull && option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '!==', '!=');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', right.value);
+        }
+        return this;
+    });
+    relation('!==');
+    relation('<');
+    relation('>');
+    relation('<=');
+    relation('>=');
+    bitwise('<<', 'shiftleft', 120);
+    bitwise('>>', 'shiftright', 120);
+    bitwise('>>>', 'shiftrightunsigned', 120);
+    infix('in', 'in', 120);
+    infix('instanceof', 'instanceof', 120);
+    infix('+', function (left, that) {
+        var right = expression(130);
+        if (left && right && left.id === '(string)' && right.id === '(string)') {
+            left.value += right.value;
+            left.character = right.character;
+            if (!option.scripturl && jx.test(left.value)) {
+                warning("JavaScript URL.", left);
+            }
+            return left;
+        }
+        that.left = left;
+        that.right = right;
+        return that;
+    }, 130);
+    prefix('+', 'num');
+    prefix('+++', function () {
+        warning("Confusing pluses.");
+        this.right = expression(150);
+        this.arity = 'unary';
+        return this;
+    });
+    infix('+++', function (left) {
+        warning("Confusing pluses.");
+        this.left = left;
+        this.right = expression(130);
+        return this;
+    }, 130);
+    infix('-', 'sub', 130);
+    prefix('-', 'neg');
+    prefix('---', function () {
+        warning("Confusing minuses.");
+        this.right = expression(150);
+        this.arity = 'unary';
+        return this;
+    });
+    infix('---', function (left) {
+        warning("Confusing minuses.");
+        this.left = left;
+        this.right = expression(130);
+        return this;
+    }, 130);
+    infix('*', 'mult', 140);
+    infix('/', 'div', 140);
+    infix('%', 'mod', 140);
+
+    suffix('++', 'postinc');
+    prefix('++', 'preinc');
+    syntax['++'].exps = true;
+
+    suffix('--', 'postdec');
+    prefix('--', 'predec');
+    syntax['--'].exps = true;
+    prefix('delete', function () {
+        var p = expression(0);
+        if (!p || (p.id !== '.' && p.id !== '[')) {
+            warning("Variables should not be deleted.");
+        }
+        this.first = p;
+        return this;
+    }).exps = true;
+
+    prefix('~', function () {
+        if (option.bitwise) {
+            warning("Unexpected '{a}'.", this, '~');
+        }
+        expression(150);
+        return this;
+    });
+
+    prefix('!', function () {
+        this.right = expression(150);
+        this.arity = 'unary';
+        if (bang[this.right.id] === true) {
+            warning("Confusing use of '{a}'.", this, '!');
+        }
+        return this;
+    });
+    prefix('typeof', 'typeof');
+    prefix('new', function () {
+        var c = expression(155), i;
+        if (c && c.id !== 'function') {
+            if (c.identifier) {
+                c['new'] = true;
+                switch (c.value) {
+                case 'Object':
+                    warning("Use the object literal notation {}.", token);
+                    break;
+                case 'Number':
+                case 'String':
+                case 'Boolean':
+                case 'Math':
+                case 'JSON':
+                    warning("Do not use {a} as a constructor.", token, c.value);
+                    break;
+                case 'Function':
+                    if (!option.evil) {
+                        warning("The Function constructor is eval.");
+                    }
+                    break;
+                case 'Date':
+                case 'RegExp':
+                    break;
+                default:
+                    if (c.id !== 'function') {
+                        i = c.value.substr(0, 1);
+                        if (option.newcap && (i < 'A' || i > 'Z')) {
+                            warning("A constructor name should start with an uppercase letter.",
+                                token);
+                        }
+                    }
+                }
+            } else {
+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
+                    warning("Bad constructor.", token);
+                }
+            }
+        } else {
+            if (!option.supernew)
+                warning("Weird construction. Delete 'new'.", this);
+        }
+        adjacent(token, nexttoken);
+        if (nexttoken.id !== '(' && !option.supernew) {
+            warning("Missing '()' invoking a constructor.");
+        }
+        this.first = c;
+        return this;
+    });
+    syntax['new'].exps = true;
+
+    prefix('void').exps = true;
+
+    infix('.', function (left, that) {
+        adjacent(prevtoken, token);
+        nobreak();
+        var m = identifier();
+        if (typeof m === 'string') {
+            countMember(m);
+        }
+        that.left = left;
+        that.right = m;
+        if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) {
+            if (option.noarg)
+                warning("Avoid arguments.{a}.", left, m);
+            else if (directive['use strict'])
+                error('Strict violation.');
+        } else if (!option.evil && left && left.value === 'document' &&
+                (m === 'write' || m === 'writeln')) {
+            warning("document.write can be a form of eval.", left);
+        }
+        if (!option.evil && (m === 'eval' || m === 'execScript')) {
+            warning('eval is evil.');
+        }
+        return that;
+    }, 160, true);
+
+    infix('(', function (left, that) {
+        if (prevtoken.id !== '}' && prevtoken.id !== ')') {
+            nobreak(prevtoken, token);
+        }
+        nospace();
+        if (option.immed && !left.immed && left.id === 'function') {
+            warning("Wrap an immediate function invocation in parentheses " +
+                "to assist the reader in understanding that the expression " +
+                "is the result of a function, and not the function itself.");
+        }
+        var n = 0,
+            p = [];
+        if (left) {
+            if (left.type === '(identifier)') {
+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+                    if (left.value !== 'Number' && left.value !== 'String' &&
+                            left.value !== 'Boolean' &&
+                            left.value !== 'Date') {
+                        if (left.value === 'Math') {
+                            warning("Math is not a function.", left);
+                        } else if (option.newcap) {
+                            warning(
+"Missing 'new' prefix when invoking a constructor.", left);
+                        }
+                    }
+                }
+            }
+        }
+        if (nexttoken.id !== ')') {
+            for (;;) {
+                p[p.length] = expression(10);
+                n += 1;
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+        }
+        advance(')');
+        nospace(prevtoken, token);
+        if (typeof left === 'object') {
+            if (left.value === 'parseInt' && n === 1) {
+                warning("Missing radix parameter.", left);
+            }
+            if (!option.evil) {
+                if (left.value === 'eval' || left.value === 'Function' ||
+                        left.value === 'execScript') {
+                    warning("eval is evil.", left);
+                } else if (p[0] && p[0].id === '(string)' &&
+                       (left.value === 'setTimeout' ||
+                        left.value === 'setInterval')) {
+                    warning(
+    "Implied eval is evil. Pass a function instead of a string.", left);
+                }
+            }
+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
+                    left.id !== '?') {
+                warning("Bad invocation.", left);
+            }
+        }
+        that.left = left;
+        return that;
+    }, 155, true).exps = true;
+
+    prefix('(', function () {
+        nospace();
+        if (nexttoken.id === 'function') {
+            nexttoken.immed = true;
+        }
+        var v = expression(0);
+        advance(')', this);
+        nospace(prevtoken, token);
+        if (option.immed && v.id === 'function') {
+            if (nexttoken.id === '(') {
+                warning(
+"Move the invocation into the parens that contain the function.", nexttoken);
+            } else {
+                warning(
+"Do not wrap function literals in parens unless they are to be immediately invoked.",
+                        this);
+            }
+        }
+        return v;
+    });
+
+    infix('[', function (left, that) {
+        nobreak(prevtoken, token);
+        nospace();
+        var e = expression(0), s;
+        if (e && e.type === '(string)') {
+            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {
+                warning("eval is evil.", that);
+            }
+            countMember(e.value);
+            if (!option.sub && ix.test(e.value)) {
+                s = syntax[e.value];
+                if (!s || !s.reserved) {
+                    warning("['{a}'] is better written in dot notation.",
+                            e, e.value);
+                }
+            }
+        }
+        advance(']', that);
+        nospace(prevtoken, token);
+        that.left = left;
+        that.right = e;
+        return that;
+    }, 160, true);
+
+    prefix('[', function () {
+        var b = token.line !== nexttoken.line;
+        this.first = [];
+        if (b) {
+            indent += option.indent;
+            if (nexttoken.from === indent + option.indent) {
+                indent += option.indent;
+            }
+        }
+        while (nexttoken.id !== '(end)') {
+            while (nexttoken.id === ',') {
+                warning("Extra comma.");
+                advance(',');
+            }
+            if (nexttoken.id === ']') {
+                break;
+            }
+            if (b && token.line !== nexttoken.line) {
+                indentation();
+            }
+            this.first.push(expression(10));
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.id === ']' && !option.es5) {
+                    warning("Extra comma.", token);
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        if (b) {
+            indent -= option.indent;
+            indentation();
+        }
+        advance(']', this);
+        return this;
+    }, 160);
+
+
+    function property_name() {
+        var id = optionalidentifier(true);
+        if (!id) {
+            if (nexttoken.id === '(string)') {
+                id = nexttoken.value;
+                advance();
+            } else if (nexttoken.id === '(number)') {
+                id = nexttoken.value.toString();
+                advance();
+            }
+        }
+        return id;
+    }
+
+
+    function functionparams() {
+        var i, t = nexttoken, p = [];
+        advance('(');
+        nospace();
+        if (nexttoken.id === ')') {
+            advance(')');
+            return;
+        }
+        for (;;) {
+            i = identifier(true);
+            p.push(i);
+            addlabel(i, 'parameter');
+            if (nexttoken.id === ',') {
+                comma();
+            } else {
+                advance(')', t);
+                nospace(prevtoken, token);
+                return p;
+            }
+        }
+    }
+
+
+    function doFunction(i, statement) {
+        var f,
+            oldOption = option,
+            oldScope  = scope;
+
+        option = Object.create(option);
+        scope = Object.create(scope);
+
+        funct = {
+            '(name)'     : i || '"' + anonname + '"',
+            '(line)'     : nexttoken.line,
+            '(context)'  : funct,
+            '(breakage)' : 0,
+            '(loopage)'  : 0,
+            '(scope)'    : scope,
+            '(statement)': statement
+        };
+        f = funct;
+        token.funct = funct;
+        functions.push(funct);
+        if (i) {
+            addlabel(i, 'function');
+        }
+        funct['(params)'] = functionparams();
+
+        block(false, false, true);
+        scope = oldScope;
+        option = oldOption;
+        funct['(last)'] = token.line;
+        funct = funct['(context)'];
+        return f;
+    }
+
+
+    (function (x) {
+        x.nud = function () {
+            var b, f, i, j, p, seen = {}, t;
+            var prop, acc = {}; // Accessor methods
+
+            function saveSetter(name, token) {
+                if (!acc[name]) {
+                    acc[name] = {};
+                }
+                acc[name].setter = true;
+                acc[name].setterToken = token;
+            }
+
+            function saveGetter(name) {
+                if (!acc[name]) {
+                    acc[name] = {};
+                }
+                acc[name].getter = true;
+            }
+
+            b = token.line !== nexttoken.line;
+            if (b) {
+                indent += option.indent;
+                if (nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+            }
+            for (;;) {
+                if (nexttoken.id === '}') {
+                    break;
+                }
+                if (b) {
+                    indentation();
+                }
+                if (nexttoken.value === 'get' && peek().id !== ':') {
+                    advance('get');
+                    if (!option.es5) {
+                        error("get/set are ES5 features.");
+                    }
+                    i = property_name();
+                    if (!i) {
+                        error("Missing property name.");
+                    }
+                    saveGetter(i);
+                    t = nexttoken;
+                    adjacent(token, nexttoken);
+                    f = doFunction();
+                    if (!option.loopfunc && funct['(loopage)']) {
+                        warning("Don't make functions within a loop.", t);
+                    }
+                    p = f['(params)'];
+                    if (p) {
+                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);
+                    }
+                    adjacent(token, nexttoken);
+                } else if (nexttoken.value === 'set' && peek().id !== ':') {
+                    advance('set');
+                    if (!option.es5) {
+                        error("get/set are ES5 features.");
+                    }
+                    i = property_name();
+                    if (!i) {
+                        error("Missing property name.");
+                    }
+                    if (acc[i] && acc[i].setter) {
+                        warning("Duplicate member '{a}'.", nexttoken, i);
+                    }
+                    saveSetter(i, nexttoken);
+                    seen[i] = false;
+                    t = nexttoken;
+                    adjacent(token, nexttoken);
+                    f = doFunction();
+                    p = f['(params)'];
+                    if (!p || p.length !== 1 || p[0] !== 'value') {
+                        warning("Expected (value) in set {a} function.", t, i);
+                    }
+                } else {
+                    i = property_name();
+                    if (typeof i !== 'string') {
+                        break;
+                    }
+                    advance(':');
+                    nonadjacent(token, nexttoken);
+                    expression(10);
+                }
+                if (seen[i] === true) {
+                    warning("Duplicate member '{a}'.", nexttoken, i);
+                }
+                seen[i] = true;
+                countMember(i);
+                if (nexttoken.id === ',') {
+                    comma();
+                    if (nexttoken.id === ',') {
+                        warning("Extra comma.", token);
+                    } else if (nexttoken.id === '}' && !option.es5) {
+                        warning("Extra comma.", token);
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (b) {
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', this);
+
+            // Check for lonely setters if in the ES5 mode.
+            if (option.es5) {
+                for (prop in acc) {
+                    if (acc.hasOwnProperty(prop) && acc[prop].setter && !acc[prop].getter) {
+                        warning("Setter is defined without getter.", acc[prop].setterToken);
+                    }
+                }
+            }
+            return this;
+        };
+        x.fud = function () {
+            error("Expected to see a statement and instead saw a block.", token);
+        };
+    }(delim('{')));
+
+// This Function is called when esnext option is set to true
+// it adds the `const` statement to JSHINT
+
+    useESNextSyntax = function () {
+        var conststatement = stmt('const', function (prefix) {
+            var id, name, value;
+
+            this.first = [];
+            for (;;) {
+                nonadjacent(token, nexttoken);
+                id = identifier();
+                if (funct[id] === "const") {
+                    warning("const '" + id + "' has already been declared");
+                }
+                if (funct['(global)'] && predefined[id] === false) {
+                    warning("Redefinition of '{a}'.", token, id);
+                }
+                addlabel(id, 'const');
+                if (prefix) {
+                    break;
+                }
+                name = token;
+                this.first.push(token);
+
+                if (nexttoken.id !== "=") {
+                    warning("const " +
+                      "'{a}' is initialized to 'undefined'.", token, id);
+                }
+
+                if (nexttoken.id === '=') {
+                    nonadjacent(token, nexttoken);
+                    advance('=');
+                    nonadjacent(token, nexttoken);
+                    if (nexttoken.id === 'undefined') {
+                        warning("It is not necessary to initialize " +
+                          "'{a}' to 'undefined'.", token, id);
+                    }
+                    if (peek(0).id === '=' && nexttoken.identifier) {
+                        error("Constant {a} was not declared correctly.",
+                                nexttoken, nexttoken.value);
+                    }
+                    value = expression(0);
+                    name.first = value;
+                }
+
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+            return this;
+        });
+        conststatement.exps = true;
+    };
+
+    var varstatement = stmt('var', function (prefix) {
+        // JavaScript does not have block scope. It only has function scope. So,
+        // declaring a variable in a block can have unexpected consequences.
+        var id, name, value;
+
+        if (funct['(onevar)'] && option.onevar) {
+            warning("Too many var statements.");
+        } else if (!funct['(global)']) {
+            funct['(onevar)'] = true;
+        }
+        this.first = [];
+        for (;;) {
+            nonadjacent(token, nexttoken);
+            id = identifier();
+            if (option.esnext && funct[id] === "const") {
+                warning("const '" + id + "' has already been declared");
+            }
+            if (funct['(global)'] && predefined[id] === false) {
+                warning("Redefinition of '{a}'.", token, id);
+            }
+            addlabel(id, 'unused');
+            if (prefix) {
+                break;
+            }
+            name = token;
+            this.first.push(token);
+            if (nexttoken.id === '=') {
+                nonadjacent(token, nexttoken);
+                advance('=');
+                nonadjacent(token, nexttoken);
+                if (nexttoken.id === 'undefined') {
+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
+                }
+                if (peek(0).id === '=' && nexttoken.identifier) {
+                    error("Variable {a} was not declared correctly.",
+                            nexttoken, nexttoken.value);
+                }
+                value = expression(0);
+                name.first = value;
+            }
+            if (nexttoken.id !== ',') {
+                break;
+            }
+            comma();
+        }
+        return this;
+    });
+    varstatement.exps = true;
+
+    blockstmt('function', function () {
+        if (inblock) {
+            warning("Function declarations should not be placed in blocks. " +
+                "Use a function expression or move the statement to the top of " +
+                "the outer function.", token);
+
+        }
+        var i = identifier();
+        if (option.esnext && funct[i] === "const") {
+            warning("const '" + i + "' has already been declared");
+        }
+        adjacent(token, nexttoken);
+        addlabel(i, 'unction');
+        doFunction(i, true);
+        if (nexttoken.id === '(' && nexttoken.line === token.line) {
+            error(
+"Function declarations are not invocable. Wrap the whole function invocation in parens.");
+        }
+        return this;
+    });
+
+    prefix('function', function () {
+        var i = optionalidentifier();
+        if (i) {
+            adjacent(token, nexttoken);
+        } else {
+            nonadjacent(token, nexttoken);
+        }
+        doFunction(i);
+        if (!option.loopfunc && funct['(loopage)']) {
+            warning("Don't make functions within a loop.");
+        }
+        return this;
+    });
+
+    blockstmt('if', function () {
+        var t = nexttoken;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        expression(20);
+        if (nexttoken.id === '=') {
+            if (!option.boss)
+                warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            expression(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true, true);
+        if (nexttoken.id === 'else') {
+            nonadjacent(token, nexttoken);
+            advance('else');
+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
+                statement(true);
+            } else {
+                block(true, true);
+            }
+        }
+        return this;
+    });
+
+    blockstmt('try', function () {
+        var b, e, s;
+
+        block(false);
+        if (nexttoken.id === 'catch') {
+            advance('catch');
+            nonadjacent(token, nexttoken);
+            advance('(');
+            s = scope;
+            scope = Object.create(s);
+            e = nexttoken.value;
+            if (nexttoken.type !== '(identifier)') {
+                warning("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, e);
+            } else {
+                addlabel(e, 'exception');
+            }
+            advance();
+            advance(')');
+            block(false);
+            b = true;
+            scope = s;
+        }
+        if (nexttoken.id === 'finally') {
+            advance('finally');
+            block(false);
+            return;
+        } else if (!b) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, 'catch', nexttoken.value);
+        }
+        return this;
+    });
+
+    blockstmt('while', function () {
+        var t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        expression(20);
+        if (nexttoken.id === '=') {
+            if (!option.boss)
+                warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            expression(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true, true);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+        return this;
+    }).labelled = true;
+
+    reserve('with');
+
+    blockstmt('switch', function () {
+        var t = nexttoken,
+            g = false;
+        funct['(breakage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        this.condition = expression(20);
+        advance(')', t);
+        nospace(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        advance('{');
+        nonadjacent(token, nexttoken);
+        indent += option.indent;
+        this.cases = [];
+        for (;;) {
+            switch (nexttoken.id) {
+            case 'case':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'case':
+                case 'continue':
+                case 'return':
+                case 'switch':
+                case 'throw':
+                    break;
+                default:
+                    // You can tell JSHint that you don't use break intentionally by
+                    // adding a comment /* falls through */ on a line just before
+                    // the next `case`.
+                    if (!ft.test(lines[nexttoken.line - 2])) {
+                        warning(
+                            "Expected a 'break' statement before 'case'.",
+                            token);
+                    }
+                }
+                indentation(-option.indent);
+                advance('case');
+                this.cases.push(expression(20));
+                g = true;
+                advance(':');
+                funct['(verb)'] = 'case';
+                break;
+            case 'default':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'continue':
+                case 'return':
+                case 'throw':
+                    break;
+                default:
+                    if (!ft.test(lines[nexttoken.line - 2])) {
+                        warning(
+                            "Expected a 'break' statement before 'default'.",
+                            token);
+                    }
+                }
+                indentation(-option.indent);
+                advance('default');
+                g = true;
+                advance(':');
+                break;
+            case '}':
+                indent -= option.indent;
+                indentation();
+                advance('}', t);
+                if (this.cases.length === 1 || this.condition.id === 'true' ||
+                        this.condition.id === 'false') {
+                    if (!option.onecase)
+                        warning("This 'switch' should be an 'if'.", this);
+                }
+                funct['(breakage)'] -= 1;
+                funct['(verb)'] = undefined;
+                return;
+            case '(end)':
+                error("Missing '{a}'.", nexttoken, '}');
+                return;
+            default:
+                if (g) {
+                    switch (token.id) {
+                    case ',':
+                        error("Each value should have its own case label.");
+                        return;
+                    case ':':
+                        g = false;
+                        statements();
+                        break;
+                    default:
+                        error("Missing ':' on a case clause.", token);
+                        return;
+                    }
+                } else {
+                    if (token.id === ':') {
+                        advance(':');
+                        error("Unexpected '{a}'.", token, ':');
+                        statements();
+                    } else {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, 'case', nexttoken.value);
+                        return;
+                    }
+                }
+            }
+        }
+    }).labelled = true;
+
+    stmt('debugger', function () {
+        if (!option.debug) {
+            warning("All 'debugger' statements should be removed.");
+        }
+        return this;
+    }).exps = true;
+
+    (function () {
+        var x = stmt('do', function () {
+            funct['(breakage)'] += 1;
+            funct['(loopage)'] += 1;
+            this.first = block(true);
+            advance('while');
+            var t = nexttoken;
+            nonadjacent(token, t);
+            advance('(');
+            nospace();
+            expression(20);
+            if (nexttoken.id === '=') {
+                if (!option.boss)
+                    warning("Expected a conditional expression and instead saw an assignment.");
+                advance('=');
+                expression(20);
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        });
+        x.labelled = true;
+        x.exps = true;
+    }());
+
+    blockstmt('for', function () {
+        var s, t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
+            if (nexttoken.id === 'var') {
+                advance('var');
+                varstatement.fud.call(varstatement, true);
+            } else {
+                switch (funct[nexttoken.value]) {
+                case 'unused':
+                    funct[nexttoken.value] = 'var';
+                    break;
+                case 'var':
+                    break;
+                default:
+                    warning("Bad for in variable '{a}'.",
+                            nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance('in');
+            expression(20);
+            advance(')', t);
+            s = block(true, true);
+            if (option.forin && s && (s.length > 1 || typeof s[0] !== 'object' ||
+                    s[0].value !== 'if')) {
+                warning("The body of a for in should be wrapped in an if statement to filter " +
+                        "unwanted properties from the prototype.", this);
+            }
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        } else {
+            if (nexttoken.id !== ';') {
+                if (nexttoken.id === 'var') {
+                    advance('var');
+                    varstatement.fud.call(varstatement);
+                } else {
+                    for (;;) {
+                        expression(0, 'for');
+                        if (nexttoken.id !== ',') {
+                            break;
+                        }
+                        comma();
+                    }
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id !== ';') {
+                expression(20);
+                if (nexttoken.id === '=') {
+                    if (!option.boss)
+                        warning("Expected a conditional expression and instead saw an assignment.");
+                    advance('=');
+                    expression(20);
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id === ';') {
+                error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, ')', ';');
+            }
+            if (nexttoken.id !== ')') {
+                for (;;) {
+                    expression(0, 'for');
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    comma();
+                }
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            block(true, true);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        }
+    }).labelled = true;
+
+
+    stmt('break', function () {
+        var v = nexttoken.value;
+
+        if (funct['(breakage)'] === 0)
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+
+        if (!option.asi)
+            nolinebreak(this);
+
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        }
+        reachable('break');
+        return this;
+    }).exps = true;
+
+
+    stmt('continue', function () {
+        var v = nexttoken.value;
+
+        if (funct['(breakage)'] === 0)
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+
+        if (!option.asi)
+            nolinebreak(this);
+
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        } else if (!funct['(loopage)']) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        reachable('continue');
+        return this;
+    }).exps = true;
+
+
+    stmt('return', function () {
+        if (this.line === nexttoken.line) {
+            if (nexttoken.id === '(regexp)')
+                warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+
+            if (nexttoken.id !== ';' && !nexttoken.reach) {
+                nonadjacent(token, nexttoken);
+                if (peek().value === "=" && !option.boss) {
+                    warningAt("Did you mean to return a conditional instead of an assignment?",
+                              token.line, token.character + 1);
+                }
+                this.first = expression(0);
+            }
+        } else if (!option.asi) {
+            nolinebreak(this); // always warn (Line breaking error)
+        }
+        reachable('return');
+        return this;
+    }).exps = true;
+
+
+    stmt('throw', function () {
+        nolinebreak(this);
+        nonadjacent(token, nexttoken);
+        this.first = expression(20);
+        reachable('throw');
+        return this;
+    }).exps = true;
+
+//  Superfluous reserved words
+
+    reserve('class');
+    reserve('const');
+    reserve('enum');
+    reserve('export');
+    reserve('extends');
+    reserve('import');
+    reserve('super');
+
+    reserve('let');
+    reserve('yield');
+    reserve('implements');
+    reserve('interface');
+    reserve('package');
+    reserve('private');
+    reserve('protected');
+    reserve('public');
+    reserve('static');
+
+
+// Parse JSON
+
+    function jsonValue() {
+
+        function jsonObject() {
+            var o = {}, t = nexttoken;
+            advance('{');
+            if (nexttoken.id !== '}') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '}' to match '{' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === '}') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    } else if (nexttoken.id !== '(string)') {
+                        warning("Expected a string and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                    }
+                    if (o[nexttoken.value] === true) {
+                        warning("Duplicate key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else if ((nexttoken.value === '__proto__' &&
+                        !option.proto) || (nexttoken.value === '__iterator__' &&
+                        !option.iterator)) {
+                        warning("The '{a}' key may produce unexpected results.",
+                            nexttoken, nexttoken.value);
+                    } else {
+                        o[nexttoken.value] = true;
+                    }
+                    advance();
+                    advance(':');
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance('}');
+        }
+
+        function jsonArray() {
+            var t = nexttoken;
+            advance('[');
+            if (nexttoken.id !== ']') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing ']' to match '[' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === ']') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    }
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(']');
+        }
+
+        switch (nexttoken.id) {
+        case '{':
+            jsonObject();
+            break;
+        case '[':
+            jsonArray();
+            break;
+        case 'true':
+        case 'false':
+        case 'null':
+        case '(number)':
+        case '(string)':
+            advance();
+            break;
+        case '-':
+            advance('-');
+            if (token.character !== nexttoken.from) {
+                warning("Unexpected space after '-'.", token);
+            }
+            adjacent(token, nexttoken);
+            advance('(number)');
+            break;
+        default:
+            error("Expected a JSON value.", nexttoken);
+        }
+    }
+
+
+// The actual JSHINT function itself.
+
+    var itself = function (s, o, g) {
+        var a, i, k;
+        JSHINT.errors = [];
+        JSHINT.undefs = [];
+        predefined = Object.create(standard);
+        combine(predefined, g || {});
+        if (o) {
+            a = o.predef;
+            if (a) {
+                if (Array.isArray(a)) {
+                    for (i = 0; i < a.length; i += 1) {
+                        predefined[a[i]] = true;
+                    }
+                } else if (typeof a === 'object') {
+                    k = Object.keys(a);
+                    for (i = 0; i < k.length; i += 1) {
+                        predefined[k[i]] = !!a[k[i]];
+                    }
+                }
+            }
+            option = o;
+        } else {
+            option = {};
+        }
+        option.indent = option.indent || 4;
+        option.maxerr = option.maxerr || 50;
+
+        tab = '';
+        for (i = 0; i < option.indent; i += 1) {
+            tab += ' ';
+        }
+        indent = 1;
+        global = Object.create(predefined);
+        scope = global;
+        funct = {
+            '(global)': true,
+            '(name)': '(global)',
+            '(scope)': scope,
+            '(breakage)': 0,
+            '(loopage)': 0
+        };
+        functions = [funct];
+        urls = [];
+        stack = null;
+        member = {};
+        membersOnly = null;
+        implied = {};
+        inblock = false;
+        lookahead = [];
+        jsonmode = false;
+        warnings = 0;
+        lex.init(s);
+        prereg = true;
+        directive = {};
+
+        prevtoken = token = nexttoken = syntax['(begin)'];
+        assume();
+
+        // combine the passed globals after we've assumed all our options
+        combine(predefined, g || {});
+
+        try {
+            advance();
+            switch (nexttoken.id) {
+            case '{':
+            case '[':
+                option.laxbreak = true;
+                jsonmode = true;
+                jsonValue();
+                break;
+            default:
+                directives();
+                if (directive["use strict"] && !option.globalstrict) {
+                    warning("Use the function form of \"use strict\".", prevtoken);
+                }
+
+                statements();
+            }
+            advance('(end)');
+        } catch (e) {
+            if (e) {
+                var nt = nexttoken || {};
+                JSHINT.errors.push({
+                    raw       : e.raw,
+                    reason    : e.message,
+                    line      : e.line || nt.line,
+                    character : e.character || nt.from
+                }, null);
+            }
+        }
+
+        for (i = 0; i < JSHINT.undefs.length; i += 1) {
+            k = JSHINT.undefs[i].slice(0);
+            scope = k.shift();
+            a = k[2];
+
+            if (typeof scope[a] !== 'string' && typeof funct[a] !== 'string') {
+                warning.apply(warning, k);
+            }
+        }
+
+        return JSHINT.errors.length === 0;
+    };
+
+    // Data summary.
+    itself.data = function () {
+
+        var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j,
+            members = [], n, unused = [], v;
+        if (itself.errors.length) {
+            data.errors = itself.errors;
+        }
+
+        if (jsonmode) {
+            data.json = true;
+        }
+
+        for (n in implied) {
+            if (is_own(implied, n)) {
+                implieds.push({
+                    name: n,
+                    line: implied[n]
+                });
+            }
+        }
+        if (implieds.length > 0) {
+            data.implieds = implieds;
+        }
+
+        if (urls.length > 0) {
+            data.urls = urls;
+        }
+
+        globals = Object.keys(scope);
+        if (globals.length > 0) {
+            data.globals = globals;
+        }
+
+        for (i = 1; i < functions.length; i += 1) {
+            f = functions[i];
+            fu = {};
+            for (j = 0; j < functionicity.length; j += 1) {
+                fu[functionicity[j]] = [];
+            }
+            for (n in f) {
+                if (is_own(f, n) && n.charAt(0) !== '(') {
+                    v = f[n];
+                    if (v === 'unction') {
+                        v = 'unused';
+                    }
+                    if (Array.isArray(fu[v])) {
+                        fu[v].push(n);
+                        if (v === 'unused') {
+                            unused.push({
+                                name: n,
+                                line: f['(line)'],
+                                'function': f['(name)']
+                            });
+                        }
+                    }
+                }
+            }
+            for (j = 0; j < functionicity.length; j += 1) {
+                if (fu[functionicity[j]].length === 0) {
+                    delete fu[functionicity[j]];
+                }
+            }
+            fu.name = f['(name)'];
+            fu.param = f['(params)'];
+            fu.line = f['(line)'];
+            fu.last = f['(last)'];
+            data.functions.push(fu);
+        }
+
+        if (unused.length > 0) {
+            data.unused = unused;
+        }
+
+        members = [];
+        for (n in member) {
+            if (typeof member[n] === 'number') {
+                data.member = member;
+                break;
+            }
+        }
+
+        return data;
+    };
+
+    itself.report = function (option) {
+        var data = itself.data();
+
+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
+
+        function detail(h, array) {
+            var b, i, singularity;
+            if (array) {
+                o.push('<div><i>' + h + '</i> ');
+                array = array.sort();
+                for (i = 0; i < array.length; i += 1) {
+                    if (array[i] !== singularity) {
+                        singularity = array[i];
+                        o.push((b ? ', ' : '') + singularity);
+                        b = true;
+                    }
+                }
+                o.push('</div>');
+            }
+        }
+
+
+        if (data.errors || data.implieds || data.unused) {
+            err = true;
+            o.push('<div id=errors><i>Error:</i>');
+            if (data.errors) {
+                for (i = 0; i < data.errors.length; i += 1) {
+                    c = data.errors[i];
+                    if (c) {
+                        e = c.evidence || '';
+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
+                                c.line + ' character ' + c.character : '') +
+                                ': ' + c.reason.entityify() +
+                                '</p><p class=evidence>' +
+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
+                                e).entityify()) + '</p>');
+                    }
+                }
+            }
+
+            if (data.implieds) {
+                s = [];
+                for (i = 0; i < data.implieds.length; i += 1) {
+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
+                        data.implieds[i].line + '</i>';
+                }
+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
+            }
+
+            if (data.unused) {
+                s = [];
+                for (i = 0; i < data.unused.length; i += 1) {
+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
+                        data.unused[i].line + '</i> <code>' +
+                        data.unused[i]['function'] + '</code>';
+                }
+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
+            }
+            if (data.json) {
+                o.push('<p>JSON: bad.</p>');
+            }
+            o.push('</div>');
+        }
+
+        if (!option) {
+
+            o.push('<br><div id=functions>');
+
+            if (data.urls) {
+                detail("URLs<br>", data.urls, '<br>');
+            }
+
+            if (data.json && !err) {
+                o.push('<p>JSON: good.</p>');
+            } else if (data.globals) {
+                o.push('<div><i>Global</i> ' +
+                        data.globals.sort().join(', ') + '</div>');
+            } else {
+                o.push('<div><i>No new global variables introduced.</i></div>');
+            }
+
+            for (i = 0; i < data.functions.length; i += 1) {
+                f = data.functions[i];
+
+                o.push('<br><div class=function><i>' + f.line + '-' +
+                        f.last + '</i> ' + (f.name || '') + '(' +
+                        (f.param ? f.param.join(', ') : '') + ')</div>');
+                detail('<big><b>Unused</b></big>', f.unused);
+                detail('Closure', f.closure);
+                detail('Variable', f['var']);
+                detail('Exception', f.exception);
+                detail('Outer', f.outer);
+                detail('Global', f.global);
+                detail('Label', f.label);
+            }
+
+            if (data.member) {
+                a = Object.keys(data.member);
+                if (a.length) {
+                    a = a.sort();
+                    m = '<br><pre id=members>/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '<br>');
+                            m = '    ';
+                            l = 1;
+                        }
+                        l += n.length + 2;
+                        if (data.member[k] === 1) {
+                            n = '<i>' + n + '</i>';
+                        }
+                        if (i < a.length - 1) {
+                            n += ', ';
+                        }
+                        m += n;
+                    }
+                    o.push(m + '<br>*/</pre>');
+                }
+                o.push('</div>');
+            }
+        }
+        return o.join('');
+    };
+
+    itself.jshint = itself;
+    itself.edition = '2011-04-16';
+
+    return itself;
+}());
+
+// Make JSHINT a Node module, if possible.
+if (typeof exports === 'object' && exports)
+    exports.JSHINT = JSHINT;
diff --git a/lint.sh b/lint.sh
new file mode 100755 (executable)
index 0000000..890820d
--- /dev/null
+++ b/lint.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+jsc_opts='maxerr:10000,devel:true,browser:true'
+rhino_opts='maxerr=10000,devel=true,browser=true'
+
+files=$(ls dygraph*.js | grep -v combined | grep -v dev.js);
+
+if [ -e /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc ]; then
+  # use JSC (Safari/JavaScriptCore) to run JSHint -- much faster than Rhino.
+  echo 'Running JSHint w/ JavaScriptCore (jsc)...'
+  for file in $files; do
+    ./jshint/env/jsc.sh $file $jsc_opts
+  done
+else
+  # fall back to Rhino.
+  echo 'Running JSHint w/ Rhino...'
+
+  for FILE in $files; do
+    LINT_RESULT=$(java -jar ./jsdoc-toolkit/java/classes/js.jar ./jshint/build/jshint-rhino.js $rhino_opts $FILE)
+    ERRORS=$(echo ${LINT_RESULT} | egrep [^\s] -c)
+    if [[ ${ERRORS} -ne 0 ]]; then
+      echo "[jshint] Error(s) in ${FILE}:"
+      printf "%s\n" "${LINT_RESULT}"
+    else
+      echo "[jshint] ${FILE} passed!"
+    fi
+  done
+fi
index ff12e57..5e0afbc 100755 (executable)
@@ -1,20 +1,29 @@
 #!/bin/bash
 # This script generates the combined JS file, pushes all content to a web site
 # and then reverts the combined file.
+
+if [ "$1" == "" ] ; then
+  echo "usage: $0 destination"
+  exit 1
+fi
+
 set -x
 site=$1
-
 # Produce dygraph-combined.js.
 ./generate-combined.sh
 
 # Generate documentation.
 ./generate-documentation.py > docs/options.html
-./generate-jsdoc.sh
+if [ -s docs/options.html ] ; then
+  ./generate-jsdoc.sh
 
-# Copy everything to the site.
-scp -r tests jsdoc $site \
-&& \
-scp dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png docs/* $site/
+  # Copy everything to the site.
+  scp -r tests jsdoc experimental $site \
+  && \
+  scp dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png docs/* $site/
+else
+  echo "generate-documentation.py failed"
+fi
 
 # Revert changes to dygraph-combined.js and docs/options.html
 git checkout dygraph-combined.js
index b8b29d7..67b730e 100644 (file)
@@ -2,12 +2,14 @@
  * A class to parse color values
  *
  * NOTE: modified by danvk. I removed the "getHelpXML" function to reduce the
- * file size.
+ * file size, added "use strict" and a few "var" declarations where needed.
  *
  * @author Stoyan Stefanov <sstoo@gmail.com>
  * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
  * @license Use it if you like it
  */
+"use strict";
+
 function RGBColor(color_string)
 {
     this.ok = false;
@@ -217,7 +219,7 @@ function RGBColor(color_string)
         var processor = color_defs[i].process;
         var bits = re.exec(color_string);
         if (bits) {
-            channels = processor(bits);
+            var channels = processor(bits);
             this.r = channels[0];
             this.g = channels[1];
             this.b = channels[2];
diff --git a/stacktrace.js b/stacktrace.js
new file mode 100644 (file)
index 0000000..4d0384d
--- /dev/null
@@ -0,0 +1,411 @@
+// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
+//                  Luke Smith http://lucassmith.name/ (2008)
+//                  Loic Dachary <loic@dachary.org> (2008)
+//                  Johan Euphrosine <proppy@aminche.com> (2008)
+//                  Oyvind Sean Kinsey http://kinsey.no/blog (2010)
+//                  Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)
+
+/**
+ * Main function giving a function stack trace with a forced or passed in Error
+ *
+ * @cfg {Error} e The error to create a stacktrace from (optional)
+ * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
+ * @return {Array} of Strings with functions, lines, files, and arguments where possible
+ */
+function printStackTrace(options) {
+    options = options || {guess: true};
+    var ex = options.e || null, guess = !!options.guess;
+    var p = new printStackTrace.implementation(), result = p.run(ex);
+    return (guess) ? p.guessAnonymousFunctions(result) : result;
+}
+
+printStackTrace.implementation = function() {
+};
+
+printStackTrace.implementation.prototype = {
+    /**
+     * @param {Error} ex The error to create a stacktrace from (optional)
+     * @param {String} mode Forced mode (optional, mostly for unit tests)
+     */
+    run: function(ex, mode) {
+        ex = ex || this.createException();
+        // examine exception properties w/o debugger
+        //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);}
+        mode = mode || this.mode(ex);
+        if (mode === 'other') {
+            return this.other(arguments.callee);
+        } else {
+            return this[mode](ex);
+        }
+    },
+
+    createException: function() {
+        try {
+            this.undef();
+        } catch (e) {
+            return e;
+        }
+    },
+
+    /**
+     * Mode could differ for different exception, e.g.
+     * exceptions in Chrome may or may not have arguments or stack.
+     *
+     * @return {String} mode of operation for the exception
+     */
+    mode: function(e) {
+        if (e['arguments'] && e.stack) {
+            return 'chrome';
+        } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) {
+            // e.message.indexOf("Backtrace:") > -1 -> opera
+            // !e.stacktrace -> opera
+            if (!e.stacktrace) {
+                return 'opera9'; // use e.message
+            }
+            // 'opera#sourceloc' in e -> opera9, opera10a
+            if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
+                return 'opera9'; // use e.message
+            }
+            // e.stacktrace && !e.stack -> opera10a
+            if (!e.stack) {
+                return 'opera10a'; // use e.stacktrace
+            }
+            // e.stacktrace && e.stack -> opera10b
+            if (e.stacktrace.indexOf("called from line") < 0) {
+                return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
+            }
+            // e.stacktrace && e.stack -> opera11
+            return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
+        } else if (e.stack) {
+            return 'firefox';
+        }
+        return 'other';
+    },
+
+    /**
+     * Given a context, function name, and callback function, overwrite it so that it calls
+     * printStackTrace() first with a callback and then runs the rest of the body.
+     *
+     * @param {Object} context of execution (e.g. window)
+     * @param {String} functionName to instrument
+     * @param {Function} function to call with a stack trace on invocation
+     */
+    instrumentFunction: function(context, functionName, callback) {
+        context = context || window;
+        var original = context[functionName];
+        context[functionName] = function instrumented() {
+            callback.call(this, printStackTrace().slice(4));
+            return context[functionName]._instrumented.apply(this, arguments);
+        };
+        context[functionName]._instrumented = original;
+    },
+
+    /**
+     * Given a context and function name of a function that has been
+     * instrumented, revert the function to it's original (non-instrumented)
+     * state.
+     *
+     * @param {Object} context of execution (e.g. window)
+     * @param {String} functionName to de-instrument
+     */
+    deinstrumentFunction: function(context, functionName) {
+        if (context[functionName].constructor === Function &&
+                context[functionName]._instrumented &&
+                context[functionName]._instrumented.constructor === Function) {
+            context[functionName] = context[functionName]._instrumented;
+        }
+    },
+
+    /**
+     * Given an Error object, return a formatted Array based on Chrome's stack string.
+     *
+     * @param e - Error object to inspect
+     * @return Array<String> of function calls, files and line numbers
+     */
+    chrome: function(e) {
+        var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, '').
+          replace(/^\s+at\s+/gm, '').
+          replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2').
+          replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n');
+        stack.pop();
+        return stack;
+    },
+
+    /**
+     * Given an Error object, return a formatted Array based on Firefox's stack string.
+     *
+     * @param e - Error object to inspect
+     * @return Array<String> of function calls, files and line numbers
+     */
+    firefox: function(e) {
+        return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
+    },
+
+    opera11: function(e) {
+        // "Error thrown at line 42, column 12 in <anonymous function>() in file://localhost/G:/js/stacktrace.js:\n"
+        // "Error thrown at line 42, column 12 in <anonymous function: createException>() in file://localhost/G:/js/stacktrace.js:\n"
+        // "called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n"
+        // "called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n"
+        var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
+        var lines = e.stacktrace.split('\n'), result = [];
+
+        for (var i = 0, len = lines.length; i < len; i += 2) {
+            var match = lineRE.exec(lines[i]);
+            if (match) {
+                var location = match[4] + ':' + match[1] + ':' + match[2];
+                var fnName = match[3] || "global code";
+                fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
+                result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
+            }
+        }
+
+        return result;
+    },
+
+    opera10b: function(e) {
+        // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
+        // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
+        // "@file://localhost/G:/js/test/functional/testcase1.html:15"
+        var ANON = '{anonymous}', lineRE = /^(.*)@(.+):(\d+)$/;
+        var lines = e.stacktrace.split('\n'), result = [];
+
+        for (var i = 0, len = lines.length; i < len; i++) {
+            var match = lineRE.exec(lines[i]);
+            if (match) {
+                var fnName = match[1]? (match[1] + '()') : "global code";
+                result.push(fnName + '@' + match[2] + ':' + match[3]);
+            }
+        }
+
+        return result;
+    },
+
+    /**
+     * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
+     *
+     * @param e - Error object to inspect
+     * @return Array<String> of function calls, files and line numbers
+     */
+    opera10a: function(e) {
+        // "  Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
+        // "  Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
+        var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
+        var lines = e.stacktrace.split('\n'), result = [];
+
+        for (var i = 0, len = lines.length; i < len; i += 2) {
+            var match = lineRE.exec(lines[i]);
+            if (match) {
+                var fnName = match[3] || ANON;
+                result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
+            }
+        }
+
+        return result;
+    },
+
+    // Opera 7.x-9.2x only!
+    opera9: function(e) {
+        // "  Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
+        // "  Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
+        var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
+        var lines = e.message.split('\n'), result = [];
+
+        for (var i = 2, len = lines.length; i < len; i += 2) {
+            var match = lineRE.exec(lines[i]);
+            if (match) {
+                result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
+            }
+        }
+
+        return result;
+    },
+
+    // Safari, IE, and others
+    other: function(curr) {
+        var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
+        while (curr && stack.length < maxStackSize) {
+            fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
+            args = Array.prototype.slice.call(curr['arguments'] || []);
+            stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
+            curr = curr.caller;
+        }
+        return stack;
+    },
+
+    /**
+     * Given arguments array as a String, subsituting type names for non-string types.
+     *
+     * @param {Arguments} object
+     * @return {Array} of Strings with stringified arguments
+     */
+    stringifyArguments: function(args) {
+        var result = [];
+        var slice = Array.prototype.slice;
+        for (var i = 0; i < args.length; ++i) {
+            var arg = args[i];
+            if (arg === undefined) {
+                result[i] = 'undefined';
+            } else if (arg === null) {
+                result[i] = 'null';
+            } else if (arg.constructor) {
+                if (arg.constructor === Array) {
+                    if (arg.length < 3) {
+                        result[i] = '[' + this.stringifyArguments(arg) + ']';
+                    } else {
+                        result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
+                    }
+                } else if (arg.constructor === Object) {
+                    result[i] = '#object';
+                } else if (arg.constructor === Function) {
+                    result[i] = '#function';
+                } else if (arg.constructor === String) {
+                    result[i] = '"' + arg + '"';
+                } else if (arg.constructor === Number) {
+                    result[i] = arg;
+                }
+            }
+        }
+        return result.join(',');
+    },
+
+    sourceCache: {},
+
+    /**
+     * @return the text from a given URL
+     */
+    ajax: function(url) {
+        var req = this.createXMLHTTPObject();
+        if (req) {
+            try {
+                req.open('GET', url, false);
+                req.send(null);
+                return req.responseText;
+            } catch (e) {
+            }
+        }
+        return '';
+    },
+
+    /**
+     * Try XHR methods in order and store XHR factory.
+     *
+     * @return <Function> XHR function or equivalent
+     */
+    createXMLHTTPObject: function() {
+        var xmlhttp, XMLHttpFactories = [
+            function() {
+                return new XMLHttpRequest();
+            }, function() {
+                return new ActiveXObject('Msxml2.XMLHTTP');
+            }, function() {
+                return new ActiveXObject('Msxml3.XMLHTTP');
+            }, function() {
+                return new ActiveXObject('Microsoft.XMLHTTP');
+            }
+        ];
+        for (var i = 0; i < XMLHttpFactories.length; i++) {
+            try {
+                xmlhttp = XMLHttpFactories[i]();
+                // Use memoization to cache the factory
+                this.createXMLHTTPObject = XMLHttpFactories[i];
+                return xmlhttp;
+            } catch (e) {
+            }
+        }
+    },
+
+    /**
+     * Given a URL, check if it is in the same domain (so we can get the source
+     * via Ajax).
+     *
+     * @param url <String> source url
+     * @return False if we need a cross-domain request
+     */
+    isSameDomain: function(url) {
+        return url.indexOf(location.hostname) !== -1;
+    },
+
+    /**
+     * Get source code from given URL if in the same domain.
+     *
+     * @param url <String> JS source URL
+     * @return <Array> Array of source code lines
+     */
+    getSource: function(url) {
+        // TODO reuse source from script tags?
+        if (!(url in this.sourceCache)) {
+            this.sourceCache[url] = this.ajax(url).split('\n');
+        }
+        return this.sourceCache[url];
+    },
+
+    guessAnonymousFunctions: function(stack) {
+        for (var i = 0; i < stack.length; ++i) {
+            var reStack = /\{anonymous\}\(.*\)@(.*)/,
+                reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
+                frame = stack[i], ref = reStack.exec(frame);
+
+            if (ref) {
+                var m = reRef.exec(ref[1]), file = m[1],
+                    lineno = m[2], charno = m[3] || 0;
+                if (file && this.isSameDomain(file) && lineno) {
+                    var functionName = this.guessAnonymousFunction(file, lineno, charno);
+                    stack[i] = frame.replace('{anonymous}', functionName);
+                }
+            }
+        }
+        return stack;
+    },
+
+    guessAnonymousFunction: function(url, lineNo, charNo) {
+        var ret;
+        try {
+            ret = this.findFunctionName(this.getSource(url), lineNo);
+        } catch (e) {
+            ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
+        }
+        return ret;
+    },
+
+    findFunctionName: function(source, lineNo) {
+        // FIXME findFunctionName fails for compressed source
+        // (more than one function on the same line)
+        // TODO use captured args
+        // function {name}({args}) m[1]=name m[2]=args
+        var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
+        // {name} = function ({args}) TODO args capture
+        // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
+        var reFunctionExpression = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/;
+        // {name} = eval()
+        var reFunctionEvaluation = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
+        // Walk backwards in the source lines until we find
+        // the line which matches one of the patterns above
+        var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
+        for (var i = 0; i < maxLines; ++i) {
+            // lineNo is 1-based, source[] is 0-based
+            line = source[lineNo - i - 1];
+            commentPos = line.indexOf('//');
+            if (commentPos >= 0) {
+                line = line.substr(0, commentPos);
+            }
+            // TODO check other types of comments? Commented code may lead to false positive
+            if (line) {
+                code = line + code;
+                m = reFunctionExpression.exec(code);
+                if (m && m[1]) {
+                    return m[1];
+                }
+                m = reFunctionDeclaration.exec(code);
+                if (m && m[1]) {
+                    //return m[1] + "(" + (m[2] || "") + ")";
+                    return m[1];
+                }
+                m = reFunctionEvaluation.exec(code);
+                if (m && m[1]) {
+                    return m[1];
+                }
+            }
+        }
+        return '(?)';
+    }
+};
index 27ea8f5..c7f1629 100644 (file)
@@ -17,7 +17,7 @@
     <p>1: Line chart with axis at zero problem:</p>
     <div id="graph1"></div>
     <script type="text/javascript">
-        new Dygraph(document.getElementById("graph1"),
+        var g1 = new Dygraph(document.getElementById("graph1"),
             "Date,Temperature\n" +
             "2008-05-07,0\n" +
             "2008-05-08,1\n" +
@@ -31,7 +31,7 @@
     <p>2: Step chart with axis at zero problem:</p>
     <div id="graphd2"></div>
     <script type="text/javascript">
-        new Dygraph(document.getElementById("graphd2"),
+        var g2 = new Dygraph(document.getElementById("graphd2"),
             "Date,Temperature\n" +
             "2008-05-07,0\n" +
             "2008-05-08,1\n" +
@@ -48,7 +48,7 @@
     <p>3: Line chart with <code>avoidMinZero</code> option:</p>
     <div id="graph3"></div>
     <script type="text/javascript">
-        new Dygraph(document.getElementById("graph3"),
+        var g3 = new Dygraph(document.getElementById("graph3"),
             "Date,Temperature\n" +
             "2008-05-07,0\n" +
             "2008-05-08,1\n" +
@@ -65,7 +65,7 @@
     <p>4: Step chart with <code>avoidMinZero</code> option:</p>
     <div id="graphd4"></div>
     <script type="text/javascript">
-        new Dygraph(document.getElementById("graphd4"),
+        var g4 = new Dygraph(document.getElementById("graphd4"),
             "Date,Temperature\n" +
             "2008-05-07,0\n" +
             "2008-05-08,1\n" +
index 5dd6d03..63851d0 100644 (file)
@@ -23,7 +23,7 @@
     <p>Hopefully this stays in its border:</p>
     <div id="bordered" style="width:600px; height:300px;"></div>
     <script type="text/javascript">
-    new Dygraph(document.getElementById('bordered'), data,
+    var g = new Dygraph(document.getElementById('bordered'), data,
     {
       labelsDivStyles: { border: '1px solid black' },
       title: 'Chart Title',
diff --git a/tests/color-cycle.html b/tests/color-cycle.html
new file mode 100644 (file)
index 0000000..fe3856f
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>color cycles</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../excanvas.js"></script>
+    <![endif]-->
+    <!--
+    For production (minified) code, use:
+    <script type="text/javascript" src="dygraph-combined.js"></script>
+    -->
+    <script type="text/javascript" src="../dygraph-dev.js"></script>
+
+  </head>
+  <body>
+    <p>Different color cycles</p>
+
+    <div id="blah"></div>
+    <p><b>Colors: </b>
+    <button type=button id=0 onClick="change(this)"> first set</button>
+    <button type=button id=1 onClick="change(this)"> second set</button>
+    <button type=button id=2 onClick="change(this)"> undefined</button>
+    </p>
+
+    <script type="text/javascript">
+      var colorSets = [
+        ['#284785', '#EE1111', '#8AE234'],
+        ['#444444', '#888888', '#DDDDDD'],
+        null
+      ]
+      chart = new Dygraph(document.getElementById("blah"),
+                          "X,a,b,c\n" +
+                          "10,12345,23456,34567\n" +
+                          "11,12345,20123,31345\n",
+                          {
+                            width: 640,
+                            height: 480,
+                            colors: colorSets[0]
+                          });
+
+      function change(el) {
+        chart.updateOptions({colors: colorSets[el.id]});
+      }
+    </script>
+  </body>
+</html>
index 608396c..96dd9a5 100644 (file)
@@ -20,7 +20,7 @@
 
     <div id="graphdiv" style="width:600px; height:300px;"></div>
     <script type="text/javascript">
-      new Dygraph(document.getElementById("graphdiv"),
+      var g = new Dygraph(document.getElementById("graphdiv"),
       [
         [ new Date("2009/12/01"), 10, 10, 10],
         [ new Date("2009/12/02"), 15, 11, 12],
index 5dd2617..6fa8762 100644 (file)
@@ -19,7 +19,7 @@
     <div id="graph"></div>
 
     <script type="text/javascript">
-      new Dygraph(document.getElementById("graph"),
+      var g = new Dygraph(document.getElementById("graph"),
                   [
                     [1, [10,  10, 100]],
                     [2, [15,  20, 110]],
index 3d66cb9..6ab0a2d 100644 (file)
@@ -20,7 +20,7 @@
     <div id="g2" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      new Dygraph(
+      var g1 = new Dygraph(
           document.getElementById("g"),
           NoisyData, {
             rollPeriod: 7,
@@ -29,7 +29,7 @@
           }
         );
 
-      new Dygraph(
+      var g2 = new Dygraph(
           document.getElementById("g2"),
           NoisyData, {
             rollPeriod: 7,
index 3525e93..7dc69f7 100644 (file)
         series: 1,
         rollPeriod: 1,
         repetitions: 1,
-        type: 'sine'
+        type: 'sine',
       };
 
       // Parse the URL for parameters. Use it to override the values hash.
index 1baf6e5..9e2a22f 100644 (file)
@@ -24,7 +24,7 @@
     <div id="div_g3" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      new Dygraph(
+      var g1 = new Dygraph(
         document.getElementById("div_g"),
         function() {
           var ret = "X,Y1,Y2\n";
@@ -36,7 +36,7 @@
         { fillGraph: true }
       );
 
-      new Dygraph(
+      var g2 = new Dygraph(
         document.getElementById("div_g2"),
         function() {
           var ret = "X,Y1,Y2\n";
@@ -49,7 +49,7 @@
         { fillGraph: true }
       );
 
-      new Dygraph(
+      var g3 = new Dygraph(
         document.getElementById("div_g3"),
         function() {
           var ret = "X,Y1,Y2\n";
index 7af7eec..dead732 100644 (file)
                   "3,2/3,17/49\n"+
                   "4,25/30,100/200";
 
-      new Dygraph(document.getElementById("dg"),
+      var g1 = new Dygraph(document.getElementById("dg"),
                   frac_str,
                   {
                     fractions: true
                   });
 
-      new Dygraph(document.getElementById("dg2"),
+      var g2 = new Dygraph(document.getElementById("dg2"),
                   frac_str,
                   {
                     fractions: true,
index 3a5e17f..259c420 100644 (file)
         new google.visualization.LineChart(
             document.getElementById('gviz')).draw(data, null);
 
-        new Dygraph.GVizChart(document.getElementById('dygraphs'))
+        var chart1 = new Dygraph.GVizChart(document.getElementById('dygraphs'))
           .draw(data, { });
 
         data = createDataTable('datetime');
-        new Dygraph.GVizChart(
+        var chart2 = new Dygraph.GVizChart(
             document.getElementById('dygraphs_datetime')).draw(data, {
             });
       }
index 8ff321b..707f6ac 100644 (file)
         new google.visualization.LineChart(
             document.getElementById('gviz')).draw(data, null);
 
-        new Dygraph.GVizChart(
+        var chart1 = new Dygraph.GVizChart(
             document.getElementById('dygraphs')).draw(data, {
             });
 
         data = createDataTable('datetime');
-        new Dygraph.GVizChart(
+        var chart2 = new Dygraph.GVizChart(
             document.getElementById('dygraphs_datetime')).draw(data, {
             });
 
index 8a569ad..588ddc4 100644 (file)
@@ -36,7 +36,7 @@
       }
 
 
-      new Dygraph(
+      var g = new Dygraph(
           document.getElementById("div_g"),
           data,
           {
index d0ac975..7e58976 100644 (file)
@@ -83,7 +83,7 @@
     </tr></table>
 
     <script type="text/javascript">
-    new Dygraph(
+    var g1 = new Dygraph(
       document.getElementById("graph"),
       [
         [1, null, 3],
index 7aa1da2..4b9bb92 100644 (file)
@@ -33,7 +33,7 @@
       <div class="codeoutput" style="float:left;">
       <div id="zoomdiv"></div>
       <script type="text/javascript">
-        new Dygraph(
+        var g = new Dygraph(
   
         // containing div
         document.getElementById("zoomdiv"),
index 6043e04..c09048d 100644 (file)
@@ -16,7 +16,7 @@
   <body>
     <div id="graph"></div>
     <script type="text/javascript">
-    new Dygraph(
+    var g = new Dygraph(
       document.getElementById("graph"),
       [
         [ 1, 10, 11],
index 7f3b361..2be418e 100644 (file)
@@ -48,9 +48,9 @@
           suffixes[magnitude];
       }
 
-      new Dygraph(document.getElementById("labelsKMB"), data, { labelsKMB: true });
-      new Dygraph(document.getElementById("labelsKMG2"), data, { labelsKMG2: true });
-      var g = new Dygraph(document.getElementById("labelsKMG2yValueFormatter"), data,
+      var g = new Dygraph(document.getElementById("labelsKMB"), data, { labelsKMB: true });
+      var g2 = new Dygraph(document.getElementById("labelsKMG2"), data, { labelsKMG2: true });
+      var g3 = new Dygraph(document.getElementById("labelsKMG2yValueFormatter"), data,
                           { labelsKMG2: true, yValueFormatter: formatValue });
     </script>
   </body>
index 475ea17..2f4cf8e 100644 (file)
         pos.push([i, 1000 + 2 * i, 1100 + i]);
       }
 
-      new Dygraph(
+      var g1 = new Dygraph(
         document.getElementById("g1"),
         negs, { labels: [ 'x', 'y1', 'y2' ] }
       );
 
-      new Dygraph(
+      var g2 = new Dygraph(
         document.getElementById("g2"),
         mixed, { labels: [ 'x', 'y1', 'y2' ] }
       );
 
-      new Dygraph(
+      var g3 = new Dygraph(
         document.getElementById("g3"),
         pos, { labels: [ 'x', 'y1', 'y2' ] }
       );
index 26a2c0f..656654a 100644 (file)
     <div id="blah2"></div>
 
     <script type="text/javascript">
-    new Dygraph(document.getElementById("blah"),
+    var g1 = new Dygraph(document.getElementById("blah"),
                 "X,Y\n10,12345\n11,12345\n",
                 { width: 640, height: 480 });
 
-    new Dygraph(document.getElementById("blah2"),
+    var g2 = new Dygraph(document.getElementById("blah2"),
 "date,10M\n" +
 "20021229,10000000.000000\n" +
 "20030105,10000000.000000\n" +
index 45de841..10ad58d 100644 (file)
       function drawVisualization() {
         data = createDataTable('date');
 
-        new Dygraph.GVizChart(
+        var chart = new Dygraph.GVizChart(
             document.getElementById('dygraphs_gviz')).draw(data, {
             });
 
-        new Dygraph(document.getElementById('dygraphs'), csv);
+        var graph = new Dygraph(document.getElementById('dygraphs'), csv);
 
       }
       google.setOnLoadCallback(drawVisualization);
index db84cbf..a6492dc 100644 (file)
@@ -24,7 +24,7 @@
       return "date," + label + "\n20091206,2659329.631743\n20091213,2772361.123362\n20091220,2737584.647191\n20091227,2720000.550414\n20100103,2910306.897977\n20100110,2901385.313093\n20100117,2903041.312099\n20100124,2966455.128911\n";
     }
 
-    new Dygraph(document.getElementById("blah"),
+    var g1 = new Dygraph(document.getElementById("blah"),
                 CSV,
                 {
                   width: 640,
@@ -55,7 +55,7 @@
           str += data[i].join(",") + "\n";
         }
 
-        new Dygraph(document.getElementById("blah2"),
+        var g2 = new Dygraph(document.getElementById("blah2"),
                     str,
                     { labels: [ "date", "zero", "non-zero" ] });
     </script>
index 834fd2f..3f40b07 100644 (file)
                 // set axis-related properties here
                 labelsKMB: true
               }
-            }
+            },
+            ylabel: 'Primary y-axis',
+            y2label: 'Secondary y-axis',
+            yAxisLabelWidth: 60
           }
       );
 
@@ -62,7 +65,9 @@
           data,
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
-            labelsKMB: true
+            labelsKMB: true,
+            ylabel: 'Primary y-axis',
+            y2label: 'Secondary y-axis',
           }
       );
 
index cb24df5..dcbaafe 100644 (file)
@@ -54,7 +54,7 @@
     </p>
 
     <script type="text/javascript">
-      new Dygraph(
+      var g2 = new Dygraph(
             document.getElementById("div_spark"),
             data, {
               drawXAxis: false,
index b008ad8..d216c97 100644 (file)
           "2009/07/12 06:00:00,4,6"
       }
 
-      new Dygraph(
+      var g1 = new Dygraph(
             document.getElementById("normal"),
             HourlyData()
           );
 
-      new Dygraph(
+      var g2 = new Dygraph(
             document.getElementById("offby2"),
             HourlyData(),
             { 
@@ -54,7 +54,7 @@
               }
             });
 
-      new Dygraph(
+      var g3 = new Dygraph(
             document.getElementById("seconds"),
             HourlyData(),
             { 
index f2fbd00..76f6ffd 100644 (file)
@@ -21,7 +21,7 @@
         (this was more of a problem before dygraphs automatically switched to scientific notation)</p>
         <div id="graph1"></div>
         <script type="text/javascript">
-            new Dygraph(
+            var g1 = new Dygraph(
                 document.getElementById("graph1"),
                 [
                     [1, 0.0],
@@ -40,7 +40,7 @@
         <p>The solution using a Y axis formatting function:</p>
         <div id="graph2"></div>
         <script type="text/javascript">
-            new Dygraph(
+            var g2 = new Dygraph(
                 document.getElementById("graph2"),
                 [
                     [1, 0.0],
@@ -67,7 +67,7 @@
         <p>Different yValueFormatter and yAxisLabelFormatter functions:</p>
         <div id="graph3"></div>
         <script type="text/javascript">
-            new Dygraph(
+            var g3 = new Dygraph(
                 document.getElementById("graph3"),
                 [
                     [1, 0.0],
index 1f4d0b1..8a94393 100644 (file)
@@ -24,7 +24,7 @@
 
     <div id="graphdiv" style="width:600px; height:300px;"></div>
     <script type="text/javascript">
-      new Dygraph(document.getElementById("graphdiv"),
+      var g = new Dygraph(document.getElementById("graphdiv"),
           document.getElementById("data").innerHTML,
           {
             customBars: true