From 758a629f806fa73483f730fb343013acd0ace078 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Sun, 25 Dec 2011 23:18:12 -0500 Subject: [PATCH] Add JSHint and make dygraphs pass its checks. JSHint is a JS Linter based on JSLint. This commit includes a script which can run it using either JSC (JavaScriptCore, available on Mac OS X) or Rhino (Java, on all other platforms). This catches issues like: - Unused variables - Uses of == where === would be better - Repeated declarations of vars. - Missing/superfluous semicolons. - Missing base param on parseInt. and many others. This did require some adjustments to coding styles (e.g. hoisting vars up out of for loops) but overall I think it's worth it. Squashed commit of the following: commit 4de410a39953da0ad1b9574eab30d5eb81a2e0f5 Author: Dan Vanderkam Date: Sun Dec 25 23:17:47 2011 -0500 track jshint build dir commit 88da991955e57438fabfbe337fd5c7ff214f7c97 Author: Dan Vanderkam Date: Sun Dec 25 23:14:03 2011 -0500 clear up unused vars and implied globals commit f48b09df35e1156ab8095a29f082014885b45465 Author: Dan Vanderkam Date: Sun Dec 25 22:47:47 2011 -0500 remove my files commit b2a58ca5743aee1422608b41cad21c8296aceb2a Author: Dan Vanderkam Date: Sun Dec 25 22:47:29 2011 -0500 remove jshint tests commit bf49732c46808752a704ee6a0a5870c6a6812e83 Author: Dan Vanderkam Date: Sun Dec 25 22:46:24 2011 -0500 add a copy of jshint; include its license in README commit 9b4baf4a36ad1beadf56f0e87e7fa4a226710121 Author: Dan Vanderkam Date: Sun Dec 25 22:42:12 2011 -0500 update lint script to choose either jsc or rhino commit dcf8db9d762575a43f4aaac8d6a7e1c5d4b8b116 Author: Dan Vanderkam Date: Sun Dec 25 22:02:48 2011 -0500 fix two-arg constructor bug & add test commit 35ad9c0aeed835ae79bd0fe04ffcfdfe5d54301c Author: Dan Vanderkam Date: Sun Dec 25 21:53:19 2011 -0500 demo works commit 012823ca2a491ab8a559815dccb5daeefb224576 Author: Dan Vanderkam Date: Sun Dec 25 21:52:15 2011 -0500 demo works commit 0f27f4daf820d08f418f33271eee08d1d80551ee Author: Dan Vanderkam Date: Sun Dec 25 21:47:10 2011 -0500 add lint script -- all JS files pass! commit 6b9fedce85a383a4776ef059b15c38a451bba023 Author: Dan Vanderkam Date: Sun Dec 25 21:46:56 2011 -0500 dygraph-canvas.js passes commit f51a2f3c440b44b0d5414acd07da60b28b4e66f2 Author: Dan Vanderkam Date: Sun Dec 25 21:13:37 2011 -0500 dygraph-utils passes commit f847e67a5f2be36cd7e1d534b47dfdc02878a594 Author: Dan Vanderkam Date: Sun Dec 25 21:03:48 2011 -0500 dygraph-tickers passes commit 1582fc1f591311ec21a71bdf19b6f3dee3fad728 Author: Dan Vanderkam Date: Sun Dec 25 20:56:51 2011 -0500 dygraph-range-selector passes commit 974083c7311c9a42161c50c8723d7f8a4ce251fb Author: Dan Vanderkam Date: Sun Dec 25 18:41:52 2011 -0500 dygraph-options-reference.js passes commit 1b88fe74a331acea77449d051482d99669dbbef2 Author: Dan Vanderkam Date: Sun Dec 25 18:38:58 2011 -0500 dygraph-layout passes commit 6b92c4e8993f1ffd56159cb1f623bc8c2cb7b3c9 Author: Dan Vanderkam Date: Sun Dec 25 18:35:35 2011 -0500 dygraph-interaction-model.js passes commit 955b58fbb3c41f8916f5d65fba0b767a61a5f3b1 Author: Dan Vanderkam Date: Sun Dec 25 18:34:08 2011 -0500 dygraph-gviz.js passes commit 41a65c43ae7f13f100468460b181be154b400853 Author: Dan Vanderkam Date: Sun Dec 25 18:29:09 2011 -0500 dygraph.js passes --- README | 6 +- auto_tests/tests/sanity.js | 8 + dygraph-canvas.js | 239 ++- dygraph-gviz.js | 16 +- dygraph-interaction-model.js | 18 +- dygraph-layout.js | 60 +- dygraph-options-reference.js | 24 +- dygraph-range-selector.js | 97 +- dygraph-tickers.js | 58 +- dygraph-utils.js | 34 +- dygraph.js | 368 ++-- jshint/CHANGELOG | 63 + jshint/Makefile | 16 + jshint/README.markdown | 71 + jshint/build/jshint-rhino.js | 4389 ++++++++++++++++++++++++++++++++++++++++++ jshint/env/jsc.js | 79 + jshint/env/jsc.sh | 30 + jshint/env/rhino.js | 75 + jshint/env/wsh.js | 181 ++ jshint/jshint.js | 4314 +++++++++++++++++++++++++++++++++++++++++ lint.sh | 27 + 21 files changed, 9748 insertions(+), 425 deletions(-) create mode 100644 jshint/CHANGELOG create mode 100644 jshint/Makefile create mode 100755 jshint/README.markdown create mode 100644 jshint/build/jshint-rhino.js create mode 100644 jshint/env/jsc.js create mode 100755 jshint/env/jsc.sh create mode 100644 jshint/env/rhino.js create mode 100644 jshint/env/wsh.js create mode 100644 jshint/jshint.js create mode 100755 lint.sh diff --git a/README b/README index 05f2edd..dd13dce 100644 --- a/README +++ b/README @@ -55,12 +55,14 @@ dygraphs uses: - 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/ @@ -71,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. diff --git a/auto_tests/tests/sanity.js b/auto_tests/tests/sanity.js index 243f523..528b7a1 100644 --- a/auto_tests/tests/sanity.js +++ b/auto_tests/tests/sanity.js @@ -101,6 +101,14 @@ SanityTestCase.prototype.testToDomYCoord = function() { } }; +/** + * 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. diff --git a/dygraph-canvas.js b/dygraph-canvas.js index e1c24b9..e2d304c 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -24,6 +24,8 @@ * @constructor */ +/*jshint globalstrict: true */ +/*global Dygraph:false,RGBColor:false */ "use strict"; var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { @@ -42,9 +44,9 @@ var 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(); @@ -77,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 { @@ -84,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 = {}; }; @@ -124,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)/); @@ -155,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 @@ -164,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); @@ -184,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 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 @@ -309,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(); @@ -319,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; @@ -328,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) { @@ -346,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"; } } @@ -372,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(); @@ -387,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'; @@ -420,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'; @@ -433,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); @@ -442,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'; @@ -451,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); @@ -467,7 +493,7 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() { height: this.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; div.style.top = box.top + 'px'; @@ -502,7 +528,7 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() { inner_div.style.top = '0px'; } - var class_div = document.createElement("div"); + class_div = document.createElement("div"); class_div.className = 'dygraph-label dygraph-ylabel'; class_div.innerHTML = this.attr_('ylabel'); @@ -531,7 +557,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; @@ -613,7 +639,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) { @@ -629,6 +657,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) { @@ -639,15 +668,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; } @@ -659,25 +688,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; @@ -686,10 +715,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; @@ -715,13 +744,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; @@ -729,23 +758,22 @@ 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) { var lastY = baseline[point.canvasx]; if (lastY === undefined) lastY = axisY; @@ -777,22 +805,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; diff --git a/dygraph-gviz.js b/dygraph-gviz.js index 7e514a6..b87fe80 100644 --- a/dygraph-gviz.js +++ b/dygraph-gviz.js @@ -17,6 +17,8 @@ * - http://dygraphs.com/tests/annotation-gviz.html */ +/*jshint globalstrict: true */ +/*global Dygraph:false */ "use strict"; /** @@ -25,7 +27,7 @@ */ Dygraph.GVizChart = function(container) { this.container = container; -} +}; Dygraph.GVizChart.prototype.draw = function(data, options) { // Clear out any existing dygraph. @@ -37,7 +39,7 @@ Dygraph.GVizChart.prototype.draw = function(data, options) { } this.date_graph = new Dygraph(this.container, data, options); -} +}; /** * Google charts compatible setSelection @@ -51,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 @@ -65,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; -} +}; diff --git a/dygraph-interaction-model.js b/dygraph-interaction-model.js index 4018769..55184ec 100644 --- a/dygraph-interaction-model.js +++ b/dygraph-interaction-model.js @@ -10,9 +10,10 @@ * @author Robert Konigsberg (konigsberg@google.com) */ +/*jshint globalstrict: true */ +/*global Dygraph:false */ "use strict"; - /** * A collection of functions to facilitate build custom interaction models. * @class @@ -33,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]; @@ -53,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; @@ -71,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. @@ -180,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); } @@ -314,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); } @@ -411,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); } } diff --git a/dygraph-layout.js b/dygraph-layout.js index 7948686..6f8f2fd 100644 --- a/dygraph-layout.js +++ b/dygraph-layout.js @@ -9,6 +9,8 @@ * dygraphs. */ +/*jshint globalstrict: true */ +/*global Dygraph:false */ "use strict"; /** @@ -29,8 +31,8 @@ */ 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 @@ -49,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. @@ -168,18 +170,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 [' + @@ -199,12 +201,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; @@ -216,8 +218,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; @@ -240,23 +242,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]); } @@ -274,13 +277,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]); @@ -302,8 +305,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; } @@ -316,7 +320,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) { @@ -331,7 +335,7 @@ DygraphLayout.prototype._evaluateAnnotations = function() { */ DygraphLayout.prototype.removeAllDatasets = function() { delete this.datasets; - this.datasets = new Array(); + this.datasets = []; }; /** @@ -343,8 +347,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")) { @@ -361,4 +365,4 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) { } return unstackedPoint; -} +}; diff --git a/dygraph-options-reference.js b/dygraph-options-reference.js index b29bb47..b5825e6 100644 --- a/dygraph-options-reference.js +++ b/dygraph-options-reference.js @@ -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 = // "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 per-axis basis." }, "xAxisLabelWidth": { "default": "50", @@ -447,7 +444,6 @@ Dygraph.OPTIONS_REFERENCE = // "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": { @@ -621,6 +617,7 @@ Dygraph.OPTIONS_REFERENCE = // // 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 +640,25 @@ Dygraph.OPTIONS_REFERENCE = // '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.'); diff --git a/dygraph-range-selector.js b/dygraph-range-selector.js index 6007d5f..18eeee0 100644 --- a/dygraph-range-selector.js +++ b/dygraph-range-selector.js @@ -6,6 +6,8 @@ * a timeline range selector widget for dygraphs. */ +/*jshint globalstrict: true */ +/*global Dygraph:false */ "use strict"; /** @@ -70,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'); @@ -141,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; @@ -165,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; @@ -173,7 +180,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { return [xDataMin, xDataMax]; }; - function onZoomStart(e) { + onZoomStart = function(e) { Dygraph.cancelEvent(e); isZooming = true; xLast = e.screenX; @@ -183,7 +190,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { self.fgcanvas_.style.cursor = 'col-resize'; }; - function onZoom(e) { + onZoom = function(e) { if (!isZooming) { return; } @@ -193,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); } @@ -212,7 +220,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { } }; - function onZoomEnd(e) { + onZoomEnd = function(e) { if (!isZooming) { return; } @@ -227,7 +235,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { } }; - function doZoom() { + doZoom = function() { try { var zoomHandleStatus = self.getZoomHandleStatus_(); self.isChangingRange_ = true; @@ -242,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; @@ -263,7 +271,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { } }; - function onPan(e) { + onPan = function(e) { if (!isPanning) { return; } @@ -301,7 +309,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { } }; - function onPanEnd(e) { + onPanEnd = function(e) { if (!isPanning) { return; } @@ -314,7 +322,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { } }; - function doPan() { + doPan = function() { try { self.isChangingRange_ = true; self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); @@ -324,7 +332,7 @@ DygraphRangeSelector.prototype.initInteraction_ = function() { } }; - function onCanvasMouseMove(e) { + onCanvasMouseMove = function(e) { if (isZooming || isPanning) { return; } @@ -352,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); @@ -379,7 +387,7 @@ DygraphRangeSelector.prototype.drawStaticLayer_ = function() { Dygraph.warn(ex); } - var margin = .5; + var margin = 0.5; this.bgcanvas_ctx_.lineWidth = 1; ctx.strokeStyle = 'gray'; ctx.beginPath(); @@ -407,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); @@ -457,43 +465,44 @@ DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() { var combinedSeries = []; var sum; var count; + var yVal, y; var mutipleValues = typeof data[0][1] != 'number'; + var i, j, k; if (mutipleValues) { sum = []; count = []; - for (var k = 0; k < data[0][1].length; k++) { + for (k = 0; k < data[0][1].length; k++) { sum.push(0); count.push(0); } 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++; } @@ -501,7 +510,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); @@ -516,8 +525,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]; } } @@ -525,9 +534,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); } @@ -535,12 +544,12 @@ 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 { @@ -637,8 +646,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, diff --git a/dygraph-tickers.js b/dygraph-tickers.js index a17519e..46fe765 100644 --- a/dygraph-tickers.js +++ b/dygraph-tickers.js @@ -58,19 +58,22 @@ * 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) { @@ -87,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 @@ -110,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; } @@ -144,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} ); } } @@ -159,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" ]; } @@ -167,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); @@ -177,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]; @@ -294,28 +300,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; @@ -328,7 +336,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) }); @@ -362,10 +370,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) diff --git a/dygraph-utils.js b/dygraph-utils.js index 3d77148..8b9dfd9 100644 --- a/dygraph-utils.js +++ b/dygraph-utils.js @@ -11,6 +11,8 @@ * 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; @@ -19,7 +21,7 @@ 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; @@ -43,7 +45,7 @@ Dygraph.log = function(severity, message) { var st; if (typeof(printStackTrace) != 'undefined') { // Remove uninteresting bits: logging functions and paths. - var st = printStackTrace({guess:false}); + st = printStackTrace({guess:false}); while (st[0].indexOf("stacktrace") != -1) { st.splice(0, 1); } @@ -317,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); }; @@ -359,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); }; @@ -414,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; } @@ -445,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; } @@ -478,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 @@ -539,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(); @@ -627,7 +633,7 @@ Dygraph.createCanvas = function() { * Android does not fully support the tag, e.g. w/r/t/ clipping. */ Dygraph.isAndroid = function() { - return /Android/.test(navigator.userAgent); + return (/Android/).test(navigator.userAgent); }; /** @@ -656,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 { diff --git a/dygraph.js b/dygraph.js index a5e1b56..74d6ce5 100644 --- a/dygraph.js +++ b/dygraph.js @@ -43,6 +43,8 @@ */ +/*jshint globalstrict: true */ +/*global DygraphRangeSelector:false, DygraphLayout:false, DygraphCanvasRenderer:false, G_vmlCanvasManager:false */ "use strict"; /** @@ -172,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()); @@ -291,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 }); @@ -316,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); @@ -356,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
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"; } } @@ -373,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 +414,9 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { * option is also specified). */ 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_; + 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'."; }; @@ -423,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 @@ -451,7 +453,7 @@ Dygraph.prototype.attr_ = function(name, seriesName) { // 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') { @@ -471,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]; } @@ -481,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]; } @@ -573,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 @@ -591,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. @@ -617,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; } @@ -633,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; } @@ -645,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: @@ -688,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; @@ -707,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 @@ -723,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; } @@ -760,7 +761,7 @@ Dygraph.prototype.fullXRange_ = function() { } else { return [0, 1]; } -} +}; /** * Returns the value in the given row and column. If the row and column exceed @@ -928,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); @@ -940,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); @@ -966,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'); @@ -1052,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; }; /** @@ -1061,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; }; /** @@ -1306,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()); } }); @@ -1320,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; } @@ -1346,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; } } @@ -1377,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); } } @@ -1387,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; } } @@ -1408,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], @@ -1459,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; @@ -1479,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]); } @@ -1487,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]) { @@ -1546,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 ? '
' : ' '); + c = this.plotter_.colors[labels[i]]; + if (html !== '') html += (sepLines ? '
' : ' '); html += "—" + labels[i] + ""; } @@ -1564,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 += "
"; 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 += " " - + pt.name + ":" - + yval; + html += " " + pt.name + + ":" + yval; } return html; }; @@ -1620,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; } @@ -1647,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; @@ -1728,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, @@ -1791,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; } } @@ -1896,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]); } @@ -1920,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; } @@ -1933,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; @@ -1944,7 +1948,7 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) { var seriesExtremes = this.extremeValues_(series); if (bars) { - for (var j=0; j seriesExtremes[1]) { seriesExtremes[1] = cumulative_y[x]; @@ -2006,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]; @@ -2037,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"); } } }; @@ -2087,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); } } @@ -2101,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 = [ @@ -2117,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; } @@ -2147,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 " + @@ -2161,9 +2164,9 @@ Dygraph.prototype.computeYAxes_ = function() { } } - 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]; } } @@ -2203,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([]); @@ -2220,7 +2223,7 @@ 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; @@ -2231,11 +2234,11 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { // 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); } } @@ -2248,16 +2251,15 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { // 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")) { @@ -2289,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 @@ -2340,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]); } } @@ -2364,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) { @@ -2390,15 +2393,15 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) { 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 { @@ -2406,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]; @@ -2423,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]; @@ -2441,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]; } @@ -2464,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 { @@ -2577,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'); @@ -2599,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; @@ -2614,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 " + @@ -2634,18 +2637,18 @@ 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), @@ -2659,7 +2662,7 @@ Dygraph.prototype.parseCSV_ = function(data) { } } 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); } } @@ -2677,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) { @@ -2694,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; @@ -2710,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); } } @@ -2736,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; } @@ -2794,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); @@ -2816,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; } @@ -2826,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) { @@ -2841,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"; @@ -2861,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) ]); } } @@ -2877,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 @@ -2912,8 +2917,8 @@ Dygraph.prototype.start_ = function() { 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); } } @@ -2948,7 +2953,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_. @@ -2958,11 +2963,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. @@ -3097,10 +3102,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 = []; } + // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs. while (this.attr_("visibility").length < this.numColumns() - 1) { - this.attr_("visibility").push(true); + this.attrs_.visibility.push(true); } return this.attr_("visibility"); }; @@ -3202,7 +3208,7 @@ Dygraph.addAnnotationRule = function() { } this.warn("Unable to add default annotation CSS rule; display may be off."); -} +}; // Older pages may still use this name. var DateGraph = Dygraph; diff --git a/jshint/CHANGELOG b/jshint/CHANGELOG new file mode 100644 index 0000000..695f0ad --- /dev/null +++ b/jshint/CHANGELOG @@ -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() 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 index 0000000..512ca3b --- /dev/null +++ b/jshint/Makefile @@ -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 index 0000000..d8e6713 --- /dev/null +++ b/jshint/README.markdown @@ -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 index 0000000..055d38c --- /dev/null +++ b/jshint/build/jshint-rhino.js @@ -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 . + + 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*!|</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, '&') + .replace(//g, '>'); + }; + } + + 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(''); + 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('
' + h + ' '); + 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('
'); + } + } + + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

Problem' + (isFinite(c.line) ? ' at line ' + + c.line + ' character ' + c.character : '') + + ': ' + c.reason.entityify() + + '

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', 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 = '
/*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 + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + 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 index 0000000..94799c9 --- /dev/null +++ b/jshint/env/jsc.js @@ -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 index 0000000..e5baf50 --- /dev/null +++ b/jshint/env/jsc.sh @@ -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 index 0000000..f0529cd --- /dev/null +++ b/jshint/env/rhino.js @@ -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 index 0000000..d9efb6a --- /dev/null +++ b/jshint/env/wsh.js @@ -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]