From 771b6873e5bd707402d6c76830a62fa673778dfe Mon Sep 17 00:00:00 2001 From: Heiko Kernbach Date: Fri, 31 May 2013 19:52:46 +0200 Subject: [PATCH] added dolphin test suite (incl. jslint) for webinterface --- html/admin/js/arango/arango.js | 117 +- .../admin/js/collections/arangoCollections.js | 50 +- html/admin/js/collections/arangoDocument.js | 4 +- html/admin/js/collections/arangoDocuments.js | 25 +- html/admin/js/collections/arangoLogs.js | 31 +- .../collections/arangoStatisticsCollection.js | 2 + .../arangoStatisticsDescriptionCollection.js | 2 + html/admin/js/models/arangoCollection.js | 3 + html/admin/js/models/arangoDocument.js | 5 +- html/admin/js/models/arangoLog.js | 3 + html/admin/js/models/arangoStatistics.js | 7 +- .../js/models/arangoStatisticsDescription.js | 7 +- html/admin/js/routers/router.js | 33 +- html/admin/js/views/aboutView.js | 3 + html/admin/js/views/appDocumentationView.js | 3 + html/admin/js/views/collectionView.js | 29 +- html/admin/js/views/collectionsItemView.js | 7 +- html/admin/js/views/collectionsView.js | 28 +- html/admin/js/views/dashboardView.js | 35 +- html/admin/js/views/documentSourceView.js | 101 +- html/admin/js/views/documentView.js | 114 +- html/admin/js/views/documentsView.js | 49 +- html/admin/js/views/footerView.js | 3 + html/admin/js/views/logsView.js | 55 +- html/admin/js/views/navigationView.js | 3 + html/admin/js/views/newCollectionView.js | 17 +- html/admin/js/views/queryView.js | 94 +- html/admin/js/views/shellView.js | 13 +- html/admin/test/dummy.html | 74 + html/admin/test/lib/jasmine-1.3.1/MIT.LICENSE | 20 + .../test/lib/jasmine-1.3.1/jasmine-html.js | 681 ++ html/admin/test/lib/jasmine-1.3.1/jasmine.css | 82 + html/admin/test/lib/jasmine-1.3.1/jasmine.js | 2600 +++++++ html/admin/test/lib/jslint.js | 6549 +++++++++++++++++ html/admin/test/lib/underscore.js | 1221 +++ html/admin/test/runnerJSLint.html | 86 + html/admin/test/specJSLint/jsLintSpec.js | 68 + 37 files changed, 11835 insertions(+), 389 deletions(-) create mode 100644 html/admin/test/dummy.html create mode 100644 html/admin/test/lib/jasmine-1.3.1/MIT.LICENSE create mode 100644 html/admin/test/lib/jasmine-1.3.1/jasmine-html.js create mode 100644 html/admin/test/lib/jasmine-1.3.1/jasmine.css create mode 100644 html/admin/test/lib/jasmine-1.3.1/jasmine.js create mode 100644 html/admin/test/lib/jslint.js create mode 100644 html/admin/test/lib/underscore.js create mode 100644 html/admin/test/runnerJSLint.html create mode 100644 html/admin/test/specJSLint/jsLintSpec.js diff --git a/html/admin/js/arango/arango.js b/html/admin/js/arango/arango.js index af2d71839c..6a636b0530 100644 --- a/html/admin/js/arango/arango.js +++ b/html/admin/js/arango/arango.js @@ -1,3 +1,7 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, window, arangoCollection, $, arangoHelper, data */ +/*global arrayContainer:true, SliderInstance:true, DomObjects:true */ + arangoHelper = { CollectionTypes: {}, systemAttributes: function () { @@ -5,8 +9,6 @@ arangoHelper = { '_id' : true, '_rev' : true, '_key' : true, - '_from' : true, - '_to' : true, '_bidirectional' : true, '_vertices' : true, '_from' : true, @@ -43,25 +45,26 @@ arangoHelper = { }, collectionApiType: function (identifier) { - if (this.CollectionTypes[identifier] == undefined) { - this.CollectionTypes[identifier] = window.arangoDocumentStore.getCollectionInfo(identifier).type; + if (this.CollectionTypes[identifier] === undefined) { + this.CollectionTypes[identifier] = window.arangoDocumentStore + .getCollectionInfo(identifier).type; } - if (this.CollectionTypes[identifier] == 3) { + if (this.CollectionTypes[identifier] === 3) { return "edge"; } return "document"; }, collectionType: function (val) { - if (! val || val.name == '') { + if (! val || val.name === '') { return "-"; } var type; - if (val.type == 2) { + if (val.type === 2) { type = "document"; } - else if (val.type == 3) { + else if (val.type === 3) { type = "edge"; } else { @@ -73,6 +76,104 @@ arangoHelper = { } return type; + }, + + FormatJSON: function (oData, sIndent) { + var self = this; + var sHTML, iCount; + if (sIndent === undefined) { + sIndent = ""; + } + var sIndentStyle = " "; + var sDataType = arangoHelper.RealTypeOf(oData); + + if (sDataType === "array") { + if (oData.length === 0) { + return "[]"; + } + sHTML = "["; + } else { + iCount = 0; + $.each(oData, function() { + iCount++; + return; + }); + if (iCount === 0) { // object is empty + return "{}"; + } + sHTML = "{"; + } + + iCount = 0; + $.each(oData, function(sKey, vValue) { + if (iCount > 0) { + sHTML += ","; + } + if (sDataType === "array") { + sHTML += ("\n" + sIndent + sIndentStyle); + } else { + sHTML += ("\n" + sIndent + sIndentStyle + JSON.stringify(sKey) + ": "); + } + + // display relevant data type + switch (arangoHelper.RealTypeOf(vValue)) { + case "array": + case "object": + sHTML += self.FormatJSON(vValue, (sIndent + sIndentStyle)); + break; + case "boolean": + case "number": + sHTML += vValue.toString(); + break; + case "null": + sHTML += "null"; + break; + case "string": + sHTML += "\"" + vValue.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; + break; + default: + sHTML += ("TYPEOF: " + typeof vValue); + } + // loop + iCount++; + }); + + // close object + if (sDataType === "array") { + sHTML += ("\n" + sIndent + "]"); + } else { + sHTML += ("\n" + sIndent + "}"); + } + + // return + return sHTML; + }, + + RealTypeOf: function (v) { + if (typeof v === "object") { + if (v === null) { + return "null"; + } + var array = []; + if (v.constructor === array.constructor) { + return "array"; + } + var date = new Date(); + if (v.constructor === date.constructor) { + return "date"; + } + var regexp = new RegExp(); + if (v.constructor === regexp.constructor) { + return "regex"; + } + return "object"; + } + return typeof v; } + + + + + }; diff --git a/html/admin/js/collections/arangoCollections.js b/html/admin/js/collections/arangoCollections.js index b13889694b..221f0101e8 100644 --- a/html/admin/js/collections/arangoCollections.js +++ b/html/admin/js/collections/arangoCollections.js @@ -1,5 +1,6 @@ /*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global require, exports */ +/*global require, exports, Backbone, window, arangoCollection, $, arangoHelper, data */ + window.arangoCollections = Backbone.Collection.extend({ url: '/_api/collection', @@ -17,38 +18,42 @@ window.arangoCollections = Backbone.Collection.extend({ }, translateStatus : function (status) { - if (status == 0) { - return 'corrupted'; + var returnString; + if (status === 0) { + returnString = 'corrupted'; } - if (status == 1) { - return 'new born collection'; + if (status === 1) { + returnString = 'new born collection'; } - else if (status == 2) { - return 'unloaded'; + else if (status === 2) { + returnString = 'unloaded'; } - else if (status == 3) { - return 'loaded'; + else if (status === 3) { + returnString = 'loaded'; } - else if (status == 4) { - return 'in the process of being unloaded'; + else if (status === 4) { + returnString = 'in the process of being unloaded'; } - else if (status == 5) { - return 'deleted'; + else if (status === 5) { + returnString = 'deleted'; } + return returnString; }, translateTypePicture : function (type) { + var returnString; if (type === 'document') { - return "img/icon_document.png"; + returnString = "img/icon_document.png"; } else if (type === 'edge') { - return "img/icon_node.png"; + returnString = "img/icon_node.png"; } else if (type === 'unknown') { - return "img/icon_unknown.png"; + returnString = "img/icon_unknown.png"; } else { - return "img/icon_arango.png"; + returnString = "img/icon_arango.png"; } + return returnString; }, parse: function(response) { var that = this; @@ -137,7 +142,7 @@ window.arangoCollections = Backbone.Collection.extend({ lValue = l.get('name').toLowerCase(); rValue = r.get('name').toLowerCase(); } - if (lValue != rValue) { + if (lValue !== rValue) { return options.sortOrder * (lValue < rValue ? -1 : 1); } return 0; @@ -173,7 +178,14 @@ window.arangoCollections = Backbone.Collection.extend({ cache: false, type: "POST", url: "/_api/collection", - data: '{"name":' + JSON.stringify(collName) + ',"waitForSync":' + JSON.stringify(wfs) + ',"isSystem":' + JSON.stringify(isSystem) + journalSizeString + ',"type":' + collType + '}', + data: + '{"name":' + JSON.stringify(collName) + + ',"waitForSync":'+ + JSON.stringify(wfs)+ + ',"isSystem":'+ + JSON.stringify(isSystem)+ + ',"type":'+ + collType + '}', contentType: "application/json", processData: false, async: false, diff --git a/html/admin/js/collections/arangoDocument.js b/html/admin/js/collections/arangoDocument.js index 4d136da83a..c8f568154f 100644 --- a/html/admin/js/collections/arangoDocument.js +++ b/html/admin/js/collections/arangoDocument.js @@ -1,3 +1,5 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, window, arangoDocument, $, arangoHelper */ window.arangoDocument = Backbone.Collection.extend({ url: '/_api/document/', model: arangoDocument, @@ -139,8 +141,6 @@ window.arangoDocument = Backbone.Collection.extend({ processData: false, success: function(data) { window.arangoDocumentStore.add(data); - //TODO: move this to view! - //window.documentSourceView.fillSourceBox(); result = true; }, error: function(data) { diff --git a/html/admin/js/collections/arangoDocuments.js b/html/admin/js/collections/arangoDocuments.js index 56183da96c..070e5fe630 100644 --- a/html/admin/js/collections/arangoDocuments.js +++ b/html/admin/js/collections/arangoDocuments.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global require, exports, window */ +/*global require, exports, window, Backbone, arangoDocument, $*/ window.arangoDocuments = Backbone.Collection.extend({ currentPage: 1, @@ -12,28 +12,28 @@ window.arangoDocuments = Backbone.Collection.extend({ url: '/_api/documents', model: arangoDocument, getFirstDocuments: function () { - if (this.currentPage != 1) { + if (this.currentPage !== 1) { var link = window.location.hash.split("/"); window.location.hash = link[0]+"/"+link[1]+"/"+link[2]+"/1"; } }, getLastDocuments: function () { - if (this.currentPage != this.totalPages) { + if (this.currentPage !== this.totalPages) { var link = window.location.hash.split("/"); window.location.hash = link[0]+"/"+link[1]+"/"+link[2]+"/"+this.totalPages; } }, getPrevDocuments: function () { - if (this.currentPage != 1) { + if (this.currentPage !== 1) { var link = window.location.hash.split("/"); - var page = parseInt(this.currentPage) - 1; + var page = parseInt(this.currentPage, null) - 1; window.location.hash = link[0]+"/"+link[1]+"/"+link[2]+"/"+page; } }, getNextDocuments: function () { - if (this.currentPage != this.totalPages) { + if (this.currentPage !== this.totalPages) { var link = window.location.hash.split("/"); - var page = parseInt(this.currentPage) + 1; + var page = parseInt(this.currentPage, null) + 1; window.location.hash = link[0]+"/"+link[1]+"/"+link[2]+"/"+page; } }, @@ -58,11 +58,10 @@ window.arangoDocuments = Backbone.Collection.extend({ }); - if (isNaN(this.currentPage) || this.currentPage == undefined || this.currentPage < 1) { + if (isNaN(this.currentPage) || this.currentPage === undefined || this.currentPage < 1) { this.currentPage = 1; } - - if (this.totalPages == 0) { + if (this.totalPages === 0) { this.totalPages = 1; } @@ -73,11 +72,13 @@ window.arangoDocuments = Backbone.Collection.extend({ type: 'PUT', async: false, url: '/_api/simple/all/', - data: '{"collection":"' + this.collectionID + '","skip":' + this.offset + ',"limit":' + String(this.documentsPerPage) + '}', + data: + '{"collection":"' + this.collectionID + '","skip":'+ + this.offset + ',"limit":' + String(this.documentsPerPage) + '}', contentType: "application/json", success: function(data) { self.clearDocuments(); - if (self.documentsCount != 0) { + if (self.documentsCount !== 0) { $.each(data.result, function(k, v) { window.arangoDocumentsStore.add({ "id": v._id, diff --git a/html/admin/js/collections/arangoLogs.js b/html/admin/js/collections/arangoLogs.js index 08228e104e..10cd320d02 100644 --- a/html/admin/js/collections/arangoLogs.js +++ b/html/admin/js/collections/arangoLogs.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, window, $, arangoLog*/ + window.arangoLogs = Backbone.Collection.extend({ url: '/_admin/log?upto=4&size=10&offset=0', parse: function(response) { @@ -9,7 +12,7 @@ window.arangoLogs = Backbone.Collection.extend({ "lid":response.lid[i], "text":response.text[i], "timestamp":response.timestamp[i], - "totalAmount":response.totalAmount, + "totalAmount":response.totalAmount }); i++; }); @@ -35,9 +38,9 @@ window.arangoLogs = Backbone.Collection.extend({ offset = 0; } - loglevel = this.showLogLevel(table); + var loglevel = this.showLogLevel(table); var url = ""; - if (loglevel == 5) { + if (loglevel === 5) { url = "/_admin/log?upto=4&size="+size+"&offset="+offset; } else { @@ -61,10 +64,22 @@ window.arangoLogs = Backbone.Collection.extend({ }, showLogLevel: function (tableid) { tableid = '#'+tableid; - if (tableid == "#critTableID") { return 1 ;} - else if (tableid == "#warnTableID") { return 2 ;} - else if (tableid == "#infoTableID") { return 3 ;} - else if (tableid == "#debugTableID") { return 4 ;} - else if (tableid == "#logTableID") { return 5 ;} + var returnVal = 0; + if (tableid === "#critTableID") { + returnVal = 1; + } + else if (tableid === "#warnTableID") { + returnVal = 2; + } + else if (tableid === "#infoTableID") { + returnVal = 3; + } + else if (tableid === "#debugTableID") { + returnVal = 4; + } + else if (tableid === "#logTableID") { + returnVal = 5; + } + return returnVal; } }); diff --git a/html/admin/js/collections/arangoStatisticsCollection.js b/html/admin/js/collections/arangoStatisticsCollection.js index 9e6b3d7e06..147220ab78 100644 --- a/html/admin/js/collections/arangoStatisticsCollection.js +++ b/html/admin/js/collections/arangoStatisticsCollection.js @@ -1,3 +1,5 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, window */ window.StatisticsCollection = Backbone.Collection.extend({ model: window.Statistics, url: "../statistics" diff --git a/html/admin/js/collections/arangoStatisticsDescriptionCollection.js b/html/admin/js/collections/arangoStatisticsDescriptionCollection.js index 58447c0386..5ae2f54412 100644 --- a/html/admin/js/collections/arangoStatisticsDescriptionCollection.js +++ b/html/admin/js/collections/arangoStatisticsDescriptionCollection.js @@ -1,3 +1,5 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, window */ window.StatisticsDescription = Backbone.Collection.extend({ model: window.StatisticsDescription, url: "../statistics-description", diff --git a/html/admin/js/models/arangoCollection.js b/html/admin/js/models/arangoCollection.js index d5151406a2..5db44790fb 100644 --- a/html/admin/js/models/arangoCollection.js +++ b/html/admin/js/models/arangoCollection.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global window, Backbone*/ + window.arangoCollection = Backbone.Model.extend({ initialize: function () { }, diff --git a/html/admin/js/models/arangoDocument.js b/html/admin/js/models/arangoDocument.js index 1efb2f1c73..c7ff150028 100644 --- a/html/admin/js/models/arangoDocument.js +++ b/html/admin/js/models/arangoDocument.js @@ -1,9 +1,12 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global window, Backbone*/ + window.arangoDocument = Backbone.Model.extend({ initialize: function () { }, urlRoot: "/_api/document", defaults: { _id: "", - _rev: "", + _rev: "" } }); diff --git a/html/admin/js/models/arangoLog.js b/html/admin/js/models/arangoLog.js index de6c5b302e..5b465af7fb 100644 --- a/html/admin/js/models/arangoLog.js +++ b/html/admin/js/models/arangoLog.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global window, Backbone*/ + window.arangoLog = Backbone.Model.extend({ initialize: function () { }, diff --git a/html/admin/js/models/arangoStatistics.js b/html/admin/js/models/arangoStatistics.js index c5f4cae8e8..774f36913f 100644 --- a/html/admin/js/models/arangoStatistics.js +++ b/html/admin/js/models/arangoStatistics.js @@ -1,9 +1,10 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global window, Backbone*/ + window.Statistics = Backbone.Model.extend({ defaults: { }, - url: function() { return "../statistics"; - }, - + } }); diff --git a/html/admin/js/models/arangoStatisticsDescription.js b/html/admin/js/models/arangoStatisticsDescription.js index b1b51be318..c5d4fecec1 100644 --- a/html/admin/js/models/arangoStatisticsDescription.js +++ b/html/admin/js/models/arangoStatisticsDescription.js @@ -1,11 +1,12 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global window, Backbone*/ + window.StatisticsDescription = Backbone.Model.extend({ defaults: { "figures" : "", "groups" : "" }, - url: function() { return "../statistics-description"; - }, - + } }); diff --git a/html/admin/js/routers/router.js b/html/admin/js/routers/router.js index 66f2440482..9d530f2b4d 100644 --- a/html/admin/js/routers/router.js +++ b/html/admin/js/routers/router.js @@ -1,3 +1,7 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, newcap: true */ +/*global window, $, Backbone, document, arangoCollection, arangoHelper, dashboardView */ +/*global FoxxInstalledListView, FoxxActiveListView*/ + $(document).ready(function() { window.Router = Backbone.Router.extend({ @@ -37,20 +41,18 @@ $(document).ready(function() { }); window.documentsView = new window.documentsView({ - collection: window.arangoDocuments, + collection: window.arangoDocuments }); window.documentView = new window.documentView({ - collection: window.arangoDocument, + collection: window.arangoDocument }); window.documentSourceView = new window.documentSourceView({ - collection: window.arangoDocument, + collection: window.arangoDocument }); window.arangoLogsStore = new window.arangoLogs(); window.arangoLogsStore.fetch({ success: function () { - if (!window.logsView) { - } window.logsView = new window.logsView({ collection: window.arangoLogsStore }); @@ -156,13 +158,13 @@ $(document).ready(function() { }); */ if (this.statisticsDescription === undefined) { - this.statisticsDescription = new window.StatisticsDescription; + this.statisticsDescription = new window.StatisticsDescription(); this.statisticsDescription.fetch({ async:false }); } if (this.statistics === undefined) { - this.statisticsCollection = new window.StatisticsCollection; + this.statisticsCollection = new window.StatisticsCollection(); //this.statisticsCollection.fetch(); } if (this.dashboardView === undefined) { @@ -171,15 +173,14 @@ $(document).ready(function() { description: this.statisticsDescription });  } - this.dashboardView.render(); }, - + graph: function() { this.graphView.render(); this.naviView.selectMenuItem('graph-menu'); }, - + applicationsAvailable: function() { if (this.foxxList === undefined) { this.foxxList = new window.FoxxCollection(); @@ -212,7 +213,9 @@ $(document).ready(function() { this.foxxList = new window.FoxxCollection(); this.foxxList.fetch({ success: function() { - var editAppView = new window.foxxEditView({model: self.foxxList.findWhere({_key: appkey})}); + var editAppView = new window.foxxEditView({ + model: self.foxxList.findWhere({_key: appkey}) + }); editAppView.render(); } }); @@ -228,12 +231,16 @@ $(document).ready(function() { this.foxxList = new window.FoxxCollection(); this.foxxList.fetch({ success: function() { - var installAppView = new window.foxxMountView({model: self.foxxList.findWhere({_key: appkey})}); + var installAppView = new window.foxxMountView({ + model: self.foxxList.findWhere({_key: appkey}) + }); installAppView.render(); } }); } else { - var installAppView = new window.foxxMountView({model: this.foxxList.findWhere({_key: appkey})}); + var installAppView = new window.foxxMountView({ + model: this.foxxList.findWhere({_key: appkey}) + }); installAppView.render(); } diff --git a/html/admin/js/views/aboutView.js b/html/admin/js/views/aboutView.js index e14625dd42..68bbf1aaad 100644 --- a/html/admin/js/views/aboutView.js +++ b/html/admin/js/views/aboutView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $*/ + var aboutView = Backbone.View.extend({ el: '#content', init: function () { diff --git a/html/admin/js/views/appDocumentationView.js b/html/admin/js/views/appDocumentationView.js index d67af3406c..394cc9d36c 100644 --- a/html/admin/js/views/appDocumentationView.js +++ b/html/admin/js/views/appDocumentationView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $*/ + window.AppDocumentationView = Backbone.View.extend({ el: '#content', diff --git a/html/admin/js/views/collectionView.js b/html/admin/js/views/collectionView.js index 10be8f6753..554dba4d54 100644 --- a/html/admin/js/views/collectionView.js +++ b/html/admin/js/views/collectionView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, stupid: true, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, window, exports, Backbone, EJS, $, arangoHelper */ + var collectionView = Backbone.View.extend({ el: '#modalPlaceholder', initialize: function () { @@ -30,7 +33,7 @@ var collectionView = Backbone.View.extend({ "keydown #change-collection-size" : "listenKey" }, listenKey: function(e) { - if (e.keyCode == 13) { + if (e.keyCode === 13) { this.saveModifiedCollection(); } }, @@ -52,13 +55,18 @@ var collectionView = Backbone.View.extend({ $('#change-collection-type').text(this.myCollection.type); $('#change-collection-status').text(this.myCollection.status); - if (this.myCollection.status == 'unloaded') { - $('#colFooter').append(''); + if (this.myCollection.status === 'unloaded') { + $('#colFooter').append( + '' + ); $('#collectionSizeBox').hide(); $('#collectionSyncBox').hide(); } - else if (this.myCollection.status == 'loaded') { - $('#colFooter').append(''); + else if (this.myCollection.status === 'loaded') { + $('#colFooter').append( + '' + ); var data = window.arangoCollectionsStore.getProperties(this.options.colId, true); this.fillLoadedModal(data); } @@ -66,7 +74,7 @@ var collectionView = Backbone.View.extend({ fillLoadedModal: function (data) { $('#collectionSizeBox').show(); $('#collectionSyncBox').show(); - if (data.waitForSync == false) { + if (data.waitForSync === false) { $('#change-collection-sync').val('false'); } else { @@ -74,7 +82,7 @@ var collectionView = Backbone.View.extend({ } var calculatedSize = data.journalSize / 1024 / 1024; $('#change-collection-size').val(calculatedSize); - $('#change-collection').modal('show') + $('#change-collection').modal('show'); }, saveModifiedCollection: function() { var newname = $('#change-collection-name').val(); @@ -87,8 +95,9 @@ var collectionView = Backbone.View.extend({ var status = this.getCollectionStatus(); if (status === 'loaded') { + var result; if (this.myCollection.name !== newname) { - var result = window.arangoCollectionsStore.renameCollection(collid, newname); + result = window.arangoCollectionsStore.renameCollection(collid, newname); } var wfs = $('#change-collection-sync').val(); @@ -107,9 +116,7 @@ var collectionView = Backbone.View.extend({ } if (result !== true) { - if (result === undefined) { - } - else { + if (result !== undefined) { arangoHelper.arangoError("Collection error: " + result); return 0; } diff --git a/html/admin/js/views/collectionsItemView.js b/html/admin/js/views/collectionsItemView.js index 4c6fb89d62..e282e744f9 100644 --- a/html/admin/js/views/collectionsItemView.js +++ b/html/admin/js/views/collectionsItemView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, window, exports, Backbone, EJS, $*/ + window.CollectionListItemView = Backbone.View.extend({ tagName: "li", @@ -24,7 +27,9 @@ window.CollectionListItemView = Backbone.View.extend({ }, selectCollection: function() { - window.App.navigate("collection/" + encodeURIComponent(this.model.get("name")) + "/documents/1", {trigger: true}); + window.App.navigate( + "collection/" + encodeURIComponent(this.model.get("name")) + "/documents/1", {trigger: true} + ); }, noop: function(event) { diff --git a/html/admin/js/views/collectionsView.js b/html/admin/js/views/collectionsView.js index 192418d30b..600df3613c 100644 --- a/html/admin/js/views/collectionsView.js +++ b/html/admin/js/views/collectionsView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, window, setTimeout, clearTimeout, $*/ + var collectionsView = Backbone.View.extend({ el: '#content', el2: '.thumbnails', @@ -15,9 +18,14 @@ var collectionsView = Backbone.View.extend({ var searchOptions = this.collection.searchOptions; - $('.thumbnails', this.el).append('
  • Add Collection
  • '); + $('.thumbnails', this.el).append( + '
  • Add Collection
  • ' + ); this.collection.getFiltered(searchOptions).forEach(function (arango_collection) { - $('.thumbnails', this.el).append(new window.CollectionListItemView({model: arango_collection}).render().el); + $('.thumbnails', this.el).append(new window.CollectionListItemView({ + model: arango_collection + }).render().el); }, this); @@ -49,7 +57,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.includeSystem = ($('#checkSystem').is(":checked") === true); - if (oldValue != searchOptions.includeSystem) { + if (oldValue !== searchOptions.includeSystem) { this.render(); } }, @@ -59,7 +67,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.includeEdge = ($('#checkEdge').is(":checked") === true); - if (oldValue != searchOptions.includeEdge) { + if (oldValue !== searchOptions.includeEdge) { this.render(); } }, @@ -69,7 +77,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.includeDocument = ($('#checkDocument').is(":checked") === true); - if (oldValue != searchOptions.includeDocument) { + if (oldValue !== searchOptions.includeDocument) { this.render(); } }, @@ -79,7 +87,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.includeLoaded = ($('#checkLoaded').is(":checked") === true); - if (oldValue != searchOptions.includeLoaded) { + if (oldValue !== searchOptions.includeLoaded) { this.render(); } }, @@ -89,7 +97,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.includeUnloaded = ($('#checkUnloaded').is(":checked") === true); - if (oldValue != searchOptions.includeUnloaded) { + if (oldValue !== searchOptions.includeUnloaded) { this.render(); } }, @@ -99,7 +107,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.sortBy = (($('#sortName').is(":checked") === true) ? 'name' : 'type'); - if (oldValue != searchOptions.sortBy) { + if (oldValue !== searchOptions.sortBy) { this.render(); } }, @@ -109,7 +117,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.sortBy = (($('#sortType').is(":checked") === true) ? 'type' : 'name'); - if (oldValue != searchOptions.sortBy) { + if (oldValue !== searchOptions.sortBy) { this.render(); } }, @@ -119,7 +127,7 @@ var collectionsView = Backbone.View.extend({ searchOptions.sortOrder = (($('#sortOrder').is(":checked") === true) ? -1 : 1); - if (oldValue != searchOptions.sortOrder) { + if (oldValue !== searchOptions.sortOrder) { this.render(); } }, diff --git a/html/admin/js/views/dashboardView.js b/html/admin/js/views/dashboardView.js index 3310f36b6e..d3d7d71a1b 100644 --- a/html/admin/js/views/dashboardView.js +++ b/html/admin/js/views/dashboardView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $, flush, window, arangoHelper, nv, d3*/ + var dashboardView = Backbone.View.extend({ el: '#content', updateInterval: 500, // 0.5 second, constant @@ -164,7 +167,7 @@ var dashboardView = Backbone.View.extend({ } }); - if (addGroup == true) { + if (addGroup === true) { self.options.description.models[0].attributes.groups.push({ "description" : "custom", "group" : "custom", @@ -201,7 +204,6 @@ var dashboardView = Backbone.View.extend({ var scale = 0.0001; for (i = 0; i < 12; ++i) { - this.units.push(scale * 1); this.units.push(scale * 2); this.units.push(scale * 2.5); this.units.push(scale * 4); @@ -216,13 +218,12 @@ var dashboardView = Backbone.View.extend({ var i = 0, n = this.units.length; while (i < n) { var unit = this.units[i++]; - if (max > unit) { - continue; - } - if (max == unit) { + if (max === unit) { break; } - return unit; + if (max < unit) { + return unit; + } } return max; @@ -285,8 +286,8 @@ var dashboardView = Backbone.View.extend({ var self = this; nv.addGraph(function() { var chart = nv.models.pieChart() - .x(function(d) { return d.label }) - .y(function(d) { return d.value }) + .x(function(d) { return d.label; }) + .y(function(d) { return d.value; }) .showLabels(true); d3.select("#detailCollectionsChart svg") @@ -305,7 +306,7 @@ var dashboardView = Backbone.View.extend({ $.each(self.collectionsStats, function(k,v) { collValues.push({ "label" : k, - "value" : v, + "value" : v }); }); @@ -368,14 +369,15 @@ var dashboardView = Backbone.View.extend({ if (self.detailGraph === identifier) { d3.select("#detailGraphChart svg") .call(chart) - .datum([ { values: self.seriesData[identifier].values, key: identifier, color: "#8AA051" } ]) + .datum([{ + values: self.seriesData[identifier].values, + key: identifier, + color: "#8AA051" + }]) .transition().duration(500); } - else { - } - //disable ticks for small charts - //disable label for small charts + //disable ticks/label for small charts d3.select("#" + identifier + "Chart svg") .call(chart) @@ -466,7 +468,8 @@ var dashboardView = Backbone.View.extend({ '
    ' + figure.name + '
    '+ '' + - '' + + '' + '' + '
    ' + '
    ' + diff --git a/html/admin/js/views/documentSourceView.js b/html/admin/js/views/documentSourceView.js index 4bbde5eb34..7efb16a393 100644 --- a/html/admin/js/views/documentSourceView.js +++ b/html/admin/js/views/documentSourceView.js @@ -1,10 +1,13 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $, window, arangoHelper, ace, arangoDocumentStore */ + var documentSourceView = Backbone.View.extend({ el: '#content', init: function () { }, events: { "click #tableView" : "tableView", - "click #saveSourceDoc" : "saveSourceDoc", + "click #saveSourceDoc" : "saveSourceDoc" }, template: new EJS({url: 'js/templates/documentSourceView.ejs'}), @@ -46,10 +49,11 @@ var documentSourceView = Backbone.View.extend({ ); }, saveSourceDoc: function() { + var editor, model, result; if (this.type === 'document') { - var editor = ace.edit("sourceEditor"); - var model = editor.getValue(); - var result = window.arangoDocumentStore.saveDocument(this.colid, this.docid, model); + editor = ace.edit("sourceEditor"); + model = editor.getValue(); + result = window.arangoDocumentStore.saveDocument(this.colid, this.docid, model); if (result === true) { arangoHelper.arangoNotification('Document saved'); } @@ -58,9 +62,9 @@ var documentSourceView = Backbone.View.extend({ } } else if (this.type === 'edge') { - var editor = ace.edit("sourceEditor"); - var model = editor.getValue(); - var result = window.arangoDocumentStore.saveEdge(this.colid, this.docid, model); + editor = ace.edit("sourceEditor"); + model = editor.getValue(); + result = window.arangoDocumentStore.saveEdge(this.colid, this.docid, model); if (result === true) { arangoHelper.arangoNotification('Edge saved'); } @@ -82,7 +86,7 @@ var documentSourceView = Backbone.View.extend({ } }); var editor = ace.edit("sourceEditor"); - editor.setValue(this.FormatJSON(data)); + editor.setValue(arangoHelper.FormatJSON(data)); }, stateReplace: function (value) { var inString = false; @@ -154,86 +158,5 @@ var documentSourceView = Backbone.View.extend({ } return output; - }, - FormatJSON: function (oData, sIndent) { - var self = this; - if (arguments.length < 2) { - var sIndent = ""; - } - var sIndentStyle = " "; - var sDataType = self.RealTypeOf(oData); - - if (sDataType == "array") { - if (oData.length == 0) { - return "[]"; - } - var sHTML = "["; - } else { - var iCount = 0; - $.each(oData, function() { - iCount++; - return; - }); - if (iCount == 0) { // object is empty - return "{}"; - } - var sHTML = "{"; - } - - var iCount = 0; - $.each(oData, function(sKey, vValue) { - if (iCount > 0) { - sHTML += ","; - } - if (sDataType == "array") { - sHTML += ("\n" + sIndent + sIndentStyle); - } else { - sHTML += ("\n" + sIndent + sIndentStyle + JSON.stringify(sKey) + ": "); - } - - // display relevant data type - switch (self.RealTypeOf(vValue)) { - case "array": - case "object": - sHTML += self.FormatJSON(vValue, (sIndent + sIndentStyle)); - break; - case "boolean": - case "number": - sHTML += vValue.toString(); - break; - case "null": - sHTML += "null"; - break; - case "string": - sHTML += "\"" + vValue.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; - break; - default: - sHTML += ("TYPEOF: " + typeof(vValue)); - } - - // loop - iCount++; - }); - - // close object - if (sDataType == "array") { - sHTML += ("\n" + sIndent + "]"); - } else { - sHTML += ("\n" + sIndent + "}"); - } - - // return - return sHTML; - }, - RealTypeOf: function (v) { - if (typeof(v) == "object") { - if (v === null) return "null"; - if (v.constructor == (new Array).constructor) return "array"; - if (v.constructor == (new Date).constructor) return "date"; - if (v.constructor == (new RegExp).constructor) return "regex"; - return "object"; - } - return typeof(v); } - }); diff --git a/html/admin/js/views/documentView.js b/html/admin/js/views/documentView.js index e35f905a19..925b9e3e44 100644 --- a/html/admin/js/views/documentView.js +++ b/html/admin/js/views/documentView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, forin: true */ +/*global require, exports, Backbone, EJS, $, window, arangoHelper, value2html */ + var documentView = Backbone.View.extend({ el: '#content', table: '#documentTableID', @@ -19,7 +22,7 @@ var documentView = Backbone.View.extend({ "click #editSecondRow" : "editSecond", "keydown .sorting_1" : "listenKey", "keydown" : "listenGlobalKey", - "blur textarea" : "checkFocus", + "blur textarea" : "checkFocus" }, checkFocus: function(e) { @@ -52,23 +55,20 @@ var documentView = Backbone.View.extend({ template: new EJS({url: 'js/templates/documentView.ejs'}), typeCheck: function (type) { + var result; if (type === 'edge') { - var result = window.arangoDocumentStore.getEdge(this.colid, this.docid); + result = window.arangoDocumentStore.getEdge(this.colid, this.docid); if (result === true) { this.initTable(); this.drawTable(); } - else if (result === false) { - } } else if (type === 'document') { - var result = window.arangoDocumentStore.getDocument(this.colid, this.docid); + result = window.arangoDocumentStore.getDocument(this.colid, this.docid); if (result === true) { this.initTable(); this.drawTable(); } - else if (result === false) { - } } }, clicked: function (a) { @@ -103,10 +103,11 @@ var documentView = Backbone.View.extend({ window.location.hash = window.location.hash + "/source"; }, saveDocument: function () { + var model, result; if (this.type === 'document') { - var model = window.arangoDocumentStore.models[0].attributes; + model = window.arangoDocumentStore.models[0].attributes; model = JSON.stringify(model); - var result = window.arangoDocumentStore.saveDocument(this.colid, this.docid, model); + result = window.arangoDocumentStore.saveDocument(this.colid, this.docid, model); if (result === true) { arangoHelper.arangoNotification('Document saved'); $('#addRow').removeClass('disabledBtn'); @@ -117,9 +118,9 @@ var documentView = Backbone.View.extend({ } } else if (this.type === 'edge') { - var model = window.arangoDocumentStore.models[0].attributes; + model = window.arangoDocumentStore.models[0].attributes; model = JSON.stringify(model); - var result = window.arangoDocumentStore.saveEdge(this.colid, this.docid, model); + result = window.arangoDocumentStore.saveEdge(this.colid, this.docid, model); if (result === true) { arangoHelper.arangoNotification('Edge saved'); $('#addRow').removeClass('disabledBtn'); @@ -150,7 +151,8 @@ var documentView = Backbone.View.extend({ ' ', '
    ', '
    ', - '' + '' ]); $.each(window.arangoDocumentStore.models[0].attributes, function(key, value) { if (arangoHelper.isSystemAttribute(key)) { @@ -171,7 +173,8 @@ var documentView = Backbone.View.extend({ self.value2html(value), JSON.stringify(value), '', - '' + '' ]); } }); @@ -196,7 +199,8 @@ var documentView = Backbone.View.extend({ this.value2html("editme"), JSON.stringify("editme"), '', - '' + '' ] ); this.makeEditable(); @@ -241,7 +245,7 @@ var documentView = Backbone.View.extend({ value2html: function (value, isReadOnly) { var self = this; var typify = function (value) { - var checked = typeof(value); + var checked = typeof value; switch(checked) { case 'number': return ("" + value + ""); @@ -253,21 +257,21 @@ var documentView = Backbone.View.extend({ if (value instanceof Array) { return ("" + self.escaped(JSON.stringify(value)) + ""); } - else { - return (""+ self.escaped(JSON.stringify(value)) + ""); - } + return (""+ self.escaped(JSON.stringify(value)) + ""); } }; return (isReadOnly ? "(read-only) " : "") + typify(value); }, escaped: function (value) { - return value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + return value.replace(/&/g, "&").replace(//g, ">") + .replace(/"/g, """).replace(/'/g, "'"); }, updateLocalDocumentStorage: function () { var data = $(this.table).dataTable().fnGetData(); var result = {}; + var row; for (row in data) { //Exclude "add-collection" row @@ -303,47 +307,43 @@ var documentView = Backbone.View.extend({ }); $('.writeable', documentEditTable.fnGetNodes()).editable(function(value, settings) { var aPos = documentEditTable.fnGetPosition(this); - if (aPos[1] == 0) { - documentEditTable.fnUpdate(self.escaped(value), aPos[0], aPos[1]); - self.updateLocalDocumentStorage(); - return value; + if (aPos[1] === 0) { + documentEditTable.fnUpdate(self.escaped(value), aPos[0], aPos[1]); + self.updateLocalDocumentStorage(); + return value; } - if (aPos[1] == 2) { + if (aPos[1] === 2) { var oldContent = JSON.parse(documentEditTable.fnGetData(aPos[0], aPos[1] + 1)); var test = self.getTypedValue(value); - if (String(value) == String(oldContent)) { + if (String(value) === String(oldContent)) { // no change return self.value2html(oldContent); } - else { - // change update hidden row - documentEditTable.fnUpdate(JSON.stringify(test), aPos[0], aPos[1] + 1); - self.updateLocalDocumentStorage(); - // return visible row - return self.value2html(test); - } + // change update hidden row + documentEditTable.fnUpdate(JSON.stringify(test), aPos[0], aPos[1] + 1); + self.updateLocalDocumentStorage(); + // return visible row + return self.value2html(test); } },{ data: function() { - $(".btn-success").click() + $(".btn-success").click(); var aPos = documentEditTable.fnGetPosition(this); var value = documentEditTable.fnGetData(aPos[0], aPos[1]); - if (aPos[1] == 0) { + if (aPos[1] === 0) { //check if this row was newly created if (value === self.currentKey) { return value; } return value; } - if (aPos[1] == 2) { + if (aPos[1] === 2) { var oldContent = documentEditTable.fnGetData(aPos[0], aPos[1] + 1); - if (typeof(oldContent) == 'object') { + if (typeof oldContent === 'object') { //grep hidden row and paste in visible row return value2html(oldContent); } - else { - return oldContent; - } + return oldContent; } }, width: "none", // if not set, each row will get bigger & bigger (Safari & Firefox) @@ -364,7 +364,7 @@ var documentView = Backbone.View.extend({ var currentKey2 = window.documentView.currentKey; var data = $('#documentTableID').dataTable().fnGetData(); $.each(data, function(key, val) { - if (val[0] == currentKey2) { + if (val[0] === currentKey2) { $('#documentTableID').dataTable().fnDeleteRow(key); $('#addRow').removeClass('disabledBtn'); } @@ -381,10 +381,10 @@ var documentView = Backbone.View.extend({ var data = $('#documentTableID').dataTable().fnGetData(); if (toCheck === '') { - $(td).addClass('validateError'); - arangoHelper.arangoNotification("Key is empty!"); - returnval = false; - return returnval; + $(td).addClass('validateError'); + arangoHelper.arangoNotification("Key is empty!"); + returnval = false; + return returnval; } $.each(data, function(key, val) { @@ -395,23 +395,21 @@ var documentView = Backbone.View.extend({ } }); } - else if ($(td).hasClass('rightCell') === true) { - } return returnval; }, getTypedValue: function (value) { value = value.replace(/(^\s+|\s+$)/g, ''); - if (value == 'true') { + if (value === 'true') { return true; } - if (value == 'false') { + if (value === 'false') { return false; } - if (value == 'null') { + if (value === 'null') { return null; } if (value.match(/^-?((\d+)?\.)?\d+$/)) { - // TODO: support exp notation + //support exp notation return parseFloat(value); } @@ -422,8 +420,8 @@ var documentView = Backbone.View.extend({ // value is an array return test; } - if (typeof(test) == 'object') { - // value is an object + if (typeof test === 'object') { + // value is an object return test; } } @@ -431,10 +429,12 @@ var documentView = Backbone.View.extend({ } // fallback: value is a string - value = value + ''; + value = String(value); - if (value !== '' && (value.substr(0, 1) != '"' || value.substr(-1) != '"')) { - arangoHelper.arangoNotification('You have entered an invalid string value. Please review and adjust it.'); + if (value !== '' && (value.substr(0, 1) !== '"' || value.substr(-1) !== '"')) { + arangoHelper.arangoNotification( + 'You have entered an invalid string value. Please review and adjust it.' + ); throw "error"; } @@ -442,7 +442,9 @@ var documentView = Backbone.View.extend({ value = JSON.parse(value); } catch (e) { - arangoHelper.arangoNotification('You have entered an invalid string value. Please review and adjust it.'); + arangoHelper.arangoNotification( + 'You have entered an invalid string value. Please review and adjust it.' + ); throw e; } return value; diff --git a/html/admin/js/views/documentsView.js b/html/admin/js/views/documentsView.js index a806c9f9ce..202d6836a8 100644 --- a/html/admin/js/views/documentsView.js +++ b/html/admin/js/views/documentsView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $, window, arangoHelper */ + var documentsView = Backbone.View.extend({ collectionID: 0, currentPage: 1, @@ -140,8 +143,9 @@ var documentsView = Backbone.View.extend({ var deleted = false; this.docid = $(self.idelement).next().text(); + var result; if (this.type === 'document') { - var result = window.arangoDocumentStore.deleteDocument(this.colid, this.docid); + result = window.arangoDocumentStore.deleteDocument(this.colid, this.docid); if (result === true) { //on success arangoHelper.arangoNotification('Document deleted'); @@ -152,7 +156,7 @@ var documentsView = Backbone.View.extend({ } } else if (this.type === 'edge') { - var result = window.arangoDocumentStore.deleteEdge(this.colid, this.docid); + result = window.arangoDocumentStore.deleteEdge(this.colid, this.docid); if (result === true) { //on success arangoHelper.arangoNotification('Edge deleted'); @@ -164,7 +168,9 @@ var documentsView = Backbone.View.extend({ } if (deleted === true) { - $('#documentsTableID').dataTable().fnDeleteRow($('#documentsTableID').dataTable().fnGetPosition(row)); + $('#documentsTableID').dataTable().fnDeleteRow( + $('#documentsTableID').dataTable().fnGetPosition(row) + ); $('#documentsTableID').dataTable().fnClearTable(); window.arangoDocumentsStore.getDocuments(this.colid, page); $('#docDeleteModal').modal('hide'); @@ -172,7 +178,7 @@ var documentsView = Backbone.View.extend({ }, clicked: function (event) { - if (this.alreadyClicked == true) { + if (this.alreadyClicked === true) { this.alreadyClicked = false; return 0; } @@ -231,9 +237,7 @@ var documentsView = Backbone.View.extend({ var tempObj = {}; $.each(value.attributes.content, function(k, v) { - if (k === '_id' || k === '_rev' || k === '_key') { - } - else { + if (k !== '_id' || k !== '_rev' || k !== '_key') { tempObj[k] = v; } }); @@ -254,17 +258,17 @@ var documentsView = Backbone.View.extend({ + '' ]); }); - $(".prettify").snippet("javascript", {style: "nedit", menu: false, startText: false, transparent: true, showNum: false}); -/* $(".prettify").tooltip({ - html: true, - placement: "top" - });*/ + $(".prettify").snippet("javascript", { + style: "nedit", + menu: false, + startText: false, + transparent: true, + showNum: false + }); this.totalPages = window.arangoDocumentsStore.totalPages; this.currentPage = window.arangoDocumentsStore.currentPage; this.documentsCount = window.arangoDocumentsStore.documentsCount; - if (this.documentsCount === 0) { - } - else { + if (this.documentsCount !== 0) { $('#documentsStatus').html( 'Showing Page '+this.currentPage+' of '+this.totalPages+ ', '+this.documentsCount+' entries' @@ -305,13 +309,19 @@ var documentsView = Backbone.View.extend({ } }; target.pagination(options); - $('#documentsToolbarF').prepend(''); - $('#documentsToolbarF').append(''); + $('#documentsToolbarF').prepend( + ''); + $('#documentsToolbarF').append( + ''); var total = $('#totalDocuments'); if (total.length > 0) { total.html("Total: " + this.documentsCount + " documents"); } else { - $('#documentsToolbarFL').append('Total: ' + this.documentsCount + ' document(s) '); + $('#documentsToolbarFL').append( + 'Total: ' + this.documentsCount + ' document(s) ' + ); } }, breadcrumb: function () { @@ -331,7 +341,8 @@ var documentsView = Backbone.View.extend({ return this.escaped(string); }, escaped: function (value) { - return value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + return value.replace(/&/g, "&").replace(//g, ">") + .replace(/"/g, """).replace(/'/g, "'"); } }); diff --git a/html/admin/js/views/footerView.js b/html/admin/js/views/footerView.js index 332e40ac7c..c5512d5e50 100644 --- a/html/admin/js/views/footerView.js +++ b/html/admin/js/views/footerView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $*/ + var footerView = Backbone.View.extend({ el: '.footer', init: function () { diff --git a/html/admin/js/views/logsView.js b/html/admin/js/views/logsView.js index eb416f5ffc..6f024760bc 100644 --- a/html/admin/js/views/logsView.js +++ b/html/admin/js/views/logsView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $, window*/ + var logsView = Backbone.View.extend({ el: '#content', offset: 0, @@ -21,12 +24,10 @@ var logsView = Backbone.View.extend({ "click #logTableID_first" : "firstTable", "click #logTableID_last" : "lastTable", "click #logTableID_prev" : "prevTable", - "click #logTableID_next" : "nextTable", + "click #logTableID_next" : "nextTable" }, firstTable: function () { - if (this.offset == 0) { - } - else { + if (this.offset !== 0) { this.offset = 0; this.page = 1; this.clearTable(); @@ -34,9 +35,7 @@ var logsView = Backbone.View.extend({ } }, lastTable: function () { - if (this.page == this.totalPages) { - } - else { + if (this.page !== this.totalPages) { this.totalPages = Math.ceil(this.totalAmount / this.size); this.page = this.totalPages; this.offset = (this.totalPages * this.size) - this.size; @@ -45,9 +44,7 @@ var logsView = Backbone.View.extend({ } }, prevTable: function () { - if (this.offset == 0) { - } - else { + if (this.offset !== 0) { this.offset = this.offset - this.size; this.page = this.page - 1; this.clearTable(); @@ -55,9 +52,7 @@ var logsView = Backbone.View.extend({ } }, nextTable: function () { - if (this.page == this.totalPages) { - } - else { + if (this.page !== this.totalPages) { this.page = this.page + 1; this.offset = this.offset + this.size; this.clearTable(); @@ -160,15 +155,21 @@ var logsView = Backbone.View.extend({ } }; target.pagination(options); - $('#logtestdiv').prepend(''); - $('#logtestdiv').append(''); + $('#logtestdiv').prepend( + '' + ); + $('#logtestdiv').append( + '' + ); }, drawTable: function () { var self = this; function format (dt) { var pad = function (n) { - return n < 10 ? '0' + n : n + return n < 10 ? '0' + n : n; }; return dt.getUTCFullYear() + '-' @@ -197,10 +198,22 @@ var logsView = Backbone.View.extend({ $('#'+this.table).dataTable().fnClearTable(); }, convertLogStatus: function (status) { - if (status === 1) { return "Error" ;} - else if (status === 2) { return "Warning" ;} - else if (status === 3) { return "Info" ;} - else if (status === 4) { return "Debug" ;} - else { return "Unknown";} + var returnString; + if (status === 1) { + returnString = "Error"; + } + else if (status === 2) { + returnString = "Warning"; + } + else if (status === 3) { + returnString = "Info"; + } + else if (status === 4) { + returnString = "Debug"; + } + else { + returnString = "Unknown"; + } + return returnString; } }); diff --git a/html/admin/js/views/navigationView.js b/html/admin/js/views/navigationView.js index 6b8509908e..6da7282816 100644 --- a/html/admin/js/views/navigationView.js +++ b/html/admin/js/views/navigationView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $*/ + var navigationView = Backbone.View.extend({ el: '.header', init: function () { diff --git a/html/admin/js/views/newCollectionView.js b/html/admin/js/views/newCollectionView.js index 1f4889a091..33542bcf3e 100644 --- a/html/admin/js/views/newCollectionView.js +++ b/html/admin/js/views/newCollectionView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $, window, arangoHelper*/ + var newCollectionView = Backbone.View.extend({ el: '#modalPlaceholder', initialize: function () { @@ -32,7 +35,7 @@ var newCollectionView = Backbone.View.extend({ }, listenKey: function(e) { - if (e.keyCode == 13) { + if (e.keyCode === 13) { this.saveNewCollection(); } }, @@ -41,7 +44,6 @@ var newCollectionView = Backbone.View.extend({ }, saveNewCollection: function(a) { - //TODO: other solution if (window.location.hash !== '#new') { return; } @@ -53,9 +55,10 @@ var newCollectionView = Backbone.View.extend({ var collType = $('#new-collection-type').val(); var collSync = $('#new-collection-sync').val(); var isSystem = (collName.substr(0, 1) === '_'); - var wfs = (collSync == "true"); + var wfs = (collSync === "true"); + var journalSizeString; - if (collSize == '') { + if (collSize === '') { journalSizeString = ''; } else { @@ -68,12 +71,14 @@ var newCollectionView = Backbone.View.extend({ return 0; } } - if (collName == '') { + if (collName === '') { arangoHelper.arangoError('No collection name entered!'); return 0; } - var returnobj = window.arangoCollectionsStore.newCollection(collName, wfs, isSystem, journalSizeString, collType); + var returnobj = window.arangoCollectionsStore.newCollection( + collName, wfs, isSystem, journalSizeString, collType + ); if (returnobj.status === true) { self.hidden(); $("#add-collection").modal('hide'); diff --git a/html/admin/js/views/queryView.js b/html/admin/js/views/queryView.js index 05ce8cbc4b..7a70796371 100644 --- a/html/admin/js/views/queryView.js +++ b/html/admin/js/views/queryView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ +/*global require, exports, Backbone, EJS, $, localStorage, ace, Storage, window, arangoHelper*/ + var queryView = Backbone.View.extend({ el: '#content', initialize: function () { @@ -7,7 +10,7 @@ var queryView = Backbone.View.extend({ events: { 'click #submitQueryIcon' : 'submitQuery', 'click #submitQueryButton' : 'submitQuery', - 'click .clearicon': 'clearOutput', + 'click .clearicon': 'clearOutput' }, clearOutput: function() { $('#queryOutput').empty(); @@ -50,7 +53,7 @@ var queryView = Backbone.View.extend({ $('#aqlEditor .ace_text-input').focus(); $.gritter.removeAll(); - if(typeof(Storage)!=="undefined") { + if(typeof Storage) { var queryContent = localStorage.getItem("queryContent"); var queryOutput = localStorage.getItem("queryOutput"); editor.setValue(queryContent); @@ -80,8 +83,8 @@ var queryView = Backbone.View.extend({ contentType: "application/json", processData: false, success: function(data) { - editor2.setValue(self.FormatJSON(data.result)); - if(typeof(Storage) !== "undefined") { + editor2.setValue(arangoHelper.FormatJSON(data.result)); + if(typeof Storage) { localStorage.setItem("queryContent", editor.getValue()); localStorage.setItem("queryOutput", editor2.getValue()); } @@ -91,7 +94,7 @@ var queryView = Backbone.View.extend({ var temp = JSON.parse(data.responseText); editor2.setValue('[' + temp.errorNum + '] ' + temp.errorMessage); - if(typeof(Storage) !== "undefined") { + if(typeof Storage) { localStorage.setItem("queryContent", editor.getValue()); localStorage.setItem("queryOutput", editor2.getValue()); } @@ -103,87 +106,6 @@ var queryView = Backbone.View.extend({ }); editor2.resize(); - }, - FormatJSON: function(oData, sIndent) { - var self = this; - if (arguments.length < 2) { - var sIndent = ""; - } - var sIndentStyle = " "; - var sDataType = self.RealTypeOf(oData); - - if (sDataType == "array") { - if (oData.length == 0) { - return "[]"; - } - var sHTML = "["; - } else { - var iCount = 0; - $.each(oData, function() { - iCount++; - return; - }); - if (iCount == 0) { // object is empty - return "{}"; - } - var sHTML = "{"; - } - - // loop through items - var iCount = 0; - $.each(oData, function(sKey, vValue) { - if (iCount > 0) { - sHTML += ","; - } - if (sDataType == "array") { - sHTML += ("\n" + sIndent + sIndentStyle); - } else { - sHTML += ("\n" + sIndent + sIndentStyle + JSON.stringify(sKey) + ": "); - } - - // display relevant data type - switch (self.RealTypeOf(vValue)) { - case "array": - case "object": - sHTML += self.FormatJSON(vValue, (sIndent + sIndentStyle)); - break; - case "boolean": - case "number": - sHTML += vValue.toString(); - break; - case "null": - sHTML += "null"; - break; - case "string": - sHTML += "\"" + vValue.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; - break; - default: - sHTML += ("TYPEOF: " + typeof(vValue)); - } - - // loop - iCount++; - }); - - // close object - if (sDataType == "array") { - sHTML += ("\n" + sIndent + "]"); - } else { - sHTML += ("\n" + sIndent + "}"); - } - - // return - return sHTML; - }, - RealTypeOf: function(v) { - if (typeof(v) == "object") { - if (v === null) return "null"; - if (v.constructor == (new Array).constructor) return "array"; - if (v.constructor == (new Date).constructor) return "date"; - if (v.constructor == (new RegExp).constructor) return "regex"; - return "object"; - } - return typeof(v); } }); diff --git a/html/admin/js/views/shellView.js b/html/admin/js/views/shellView.js index b11b380361..710dfdbd52 100644 --- a/html/admin/js/views/shellView.js +++ b/html/admin/js/views/shellView.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, evil: true, es5:true */ +/*global require, exports, Backbone, EJS, $, window, ace, jqconsole, handler, help, location*/ + var shellView = Backbone.View.extend({ el: '#content', events: { @@ -28,7 +31,7 @@ var shellView = Backbone.View.extend({ }, renderEditor: function () { var editor = ace.edit("editor"); - editor.resize() + editor.resize(); }, editor: function () { var editor = ace.edit("editor"); @@ -93,13 +96,13 @@ var shellView = Backbone.View.extend({ jqconsole.Prompt(true, handler, function(command) { // Continue line if can't compile the command. try { - Function(command); - } catch (e) { + var test = new Function(command); + } + catch (e) { if (/[\[\{\(]$/.test(command)) { return 1; - } else { - return 0; } + return 0; } return false; }); diff --git a/html/admin/test/dummy.html b/html/admin/test/dummy.html new file mode 100644 index 0000000000..ea6fc78112 --- /dev/null +++ b/html/admin/test/dummy.html @@ -0,0 +1,74 @@ + + + + Jasmine Test JSLint + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/html/admin/test/lib/jasmine-1.3.1/MIT.LICENSE b/html/admin/test/lib/jasmine-1.3.1/MIT.LICENSE new file mode 100644 index 0000000000..7c435baaec --- /dev/null +++ b/html/admin/test/lib/jasmine-1.3.1/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/html/admin/test/lib/jasmine-1.3.1/jasmine-html.js b/html/admin/test/lib/jasmine-1.3.1/jasmine-html.js new file mode 100644 index 0000000000..543d56963e --- /dev/null +++ b/html/admin/test/lib/jasmine-1.3.1/jasmine-html.js @@ -0,0 +1,681 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + setExceptionHandling(); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView && reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = jasmine.HtmlReporter.parameters(doc); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}, + self.createDom('span', { className: 'exceptions' }, + self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } + + function noTryCatch() { + return window.location.search.match(/catch=false/); + } + + function searchWithCatch() { + var params = jasmine.HtmlReporter.parameters(window.document); + var removed = false; + var i = 0; + + while (!removed && i < params.length) { + if (params[i].match(/catch=/)) { + params.splice(i, 1); + removed = true; + } + i++; + } + if (jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + + return params.join("&"); + } + + function setExceptionHandling() { + var chxCatch = document.getElementById('no_try_catch'); + + if (noTryCatch()) { + chxCatch.setAttribute('checked', true); + jasmine.CATCH_EXCEPTIONS = false; + } + chxCatch.onclick = function() { + window.location.search = searchWithCatch(); + }; + } +}; +jasmine.HtmlReporter.parameters = function(doc) { + var paramStr = doc.location.search.substring(1); + var params = []; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + } + return params; +} +jasmine.HtmlReporter.sectionLink = function(sectionName) { + var link = '?'; + var params = []; + + if (sectionName) { + params.push('spec=' + encodeURIComponent(sectionName)); + } + if (!jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + if (params.length > 0) { + link += params.join("&"); + } + + return link; +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/html/admin/test/lib/jasmine-1.3.1/jasmine.css b/html/admin/test/lib/jasmine-1.3.1/jasmine.css new file mode 100644 index 0000000000..8c008dc722 --- /dev/null +++ b/html/admin/test/lib/jasmine-1.3.1/jasmine.css @@ -0,0 +1,82 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/html/admin/test/lib/jasmine-1.3.1/jasmine.js b/html/admin/test/lib/jasmine-1.3.1/jasmine.js new file mode 100644 index 0000000000..6b3459b913 --- /dev/null +++ b/html/admin/test/lib/jasmine-1.3.1/jasmine.js @@ -0,0 +1,2600 @@ +var isCommonJS = typeof window == "undefined" && typeof exports == "object"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Maximum levels of nesting that will be included when an object is pretty-printed + */ +jasmine.MAX_PRETTY_PRINT_DEPTH = 40; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +/** + * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. + * Set to false to let the exception bubble up in the browser. + * + */ +jasmine.CATCH_EXCEPTIONS = true; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the + * attributes on the object. + * + * @example + * // don't care about any other attributes than foo. + * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); + * + * @param sample {Object} sample + * @returns matchable object for the sample + */ +jasmine.objectContaining = function (sample) { + return new jasmine.Matchers.ObjectContaining(sample); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + * @return {jasmine.Matchers} + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.source != b.source) + mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); + + if (a.ignoreCase != b.ignoreCase) + mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.global != b.global) + mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.multiline != b.multiline) + mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.sticky != b.sticky) + mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); + + return (mismatchValues.length === 0); +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a.jasmineMatches) { + return a.jasmineMatches(b); + } + + if (b.jasmineMatches) { + return b.jasmineMatches(a); + } + + if (a instanceof jasmine.Matchers.ObjectContaining) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.ObjectContaining) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (a instanceof RegExp && b instanceof RegExp) { + return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + if (!jasmine.CATCH_EXCEPTIONS) { + this.func.apply(this.spec); + } + else { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that compares the actual to NaN. + */ +jasmine.Matchers.prototype.toBeNaN = function() { + this.message = function() { + return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; + }; + + return (this.actual !== this.actual); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; + var positiveMessage = ""; + if (this.actual.callCount === 0) { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; + } else { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') + } + return [positiveMessage, invertedMessage]; + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision, as number of decimal places + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} [expected] + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineToString = function() { + return ''; +}; + +jasmine.Matchers.ObjectContaining = function (sample) { + this.sample = sample; +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var env = jasmine.getEnv(); + + var hasKey = function(obj, keyName) { + return obj != null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in this.sample) { + if (!hasKey(other, property) && hasKey(this.sample, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { + return ""; +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!obj.hasOwnProperty(property)) continue; + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Array"); + return; + } + + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Object"); + return; + } + + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + + // parallel to blocks. each true value in this array means the block will + // get executed even if we abort + this.ensured = []; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.unshift(block); + this.ensured.unshift(ensure); +}; + +jasmine.Queue.prototype.add = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.push(block); + this.ensured.push(ensure); +}; + +jasmine.Queue.prototype.insertNext = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.ensured.splice((this.index + this.offset + 1), 0, ensure); + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this), true); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 3, + "build": 1, + "revision": 1354556913 +}; diff --git a/html/admin/test/lib/jslint.js b/html/admin/test/lib/jslint.js new file mode 100644 index 0000000000..5b0c0e3ff6 --- /dev/null +++ b/html/admin/test/lib/jslint.js @@ -0,0 +1,6549 @@ +// jslint.js +// 2013-02-18 + +// 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. + +// WARNING: JSLint will hurt your feelings. + +// JSLINT is a global function. It takes two parameters. + +// var myResult = JSLINT(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 HTML text, or a JSON text, or a CSS text. + +// The second parameter is an optional object of options that control the +// operation of JSLINT. 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, JSLINT returns true. Otherwise, it returns false. + +// If false, you can inspect JSLINT.errors to find out the problems. +// JSLINT.errors is an array of objects containing these properties: + +// { +// 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 stopping error was found, a null will be the last element of the +// JSLINT.errors array. A stopping error means that JSLint was not confident +// enough to continue. It does not necessarily mean that the error was +// especially heinous. + +// You can request a data structure that contains JSLint's results. + +// var myData = JSLINT.data(); + +// It returns a structure with this form: + +// { +// errors: [ +// { +// line: NUMBER, +// character: NUMBER, +// reason: STRING, +// evidence: STRING +// } +// ], +// functions: [ +// { +// name: STRING, +// line: NUMBER, +// last: NUMBER, +// params: [ +// { +// string: STRING +// } +// ], +// closure: [ +// STRING +// ], +// var: [ +// STRING +// ], +// exception: [ +// STRING +// ], +// outer: [ +// STRING +// ], +// unused: [ +// STRING +// ], +// undef: [ +// STRING +// ], +// global: [ +// STRING +// ], +// label: [ +// STRING +// ] +// } +// ], +// globals: [ +// STRING +// ], +// member: { +// STRING: NUMBER +// }, +// urls: [ +// STRING +// ], +// json: BOOLEAN +// } + +// Empty arrays will not be included. + +// 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 . It should be given the result of the +// JSLINT.data function. + +// var myReport = JSLINT.report(data); + +// You can request an HTML error report. + +// var myErrorReport = JSLINT.error_report(data); + +// You can obtain an object containing all of the properties found in the +// file. JSLINT.property contains an object containing a key for each +// property used in the program, the value being the number of times that +// property name was used in the file. + +// You can request a properties report, which produces a list of the program's +// properties in the form of a /*properties*/ declaration. + +// var myPropertyReport = JSLINT.properties_report(JSLINT.property); + +// You can obtain the parse tree that JSLint constructed while parsing. The +// latest tree is kept in JSLINT.tree. A nice stringification can be produced +// with + +// JSON.stringify(JSLINT.tree, [ +// 'string', 'arity', 'name', 'first', +// 'second', 'third', 'block', 'else' +// ], 4)); + +// You can request a context coloring table. It contains information that can be +// applied to the file that was analyzed. Context coloring colors functions +// based on their nesting level, and variables on the color of the functions +// in which they are defined. + +// var myColorization = JSLINT.color(data); + +// It returns an array containing objects of this form: + +// { +// from: COLUMN, +// thru: COLUMN, +// line: ROW, +// level: 0 or higher +// } + +// JSLint provides three inline directives. They look like slashstar comments, +// and allow for setting options, declaring global variables, and establishing a +// set of allowed property names. + +// These directives respect function scope. + +// The jslint directive is a special comment that can set one or more options. +// For example: + +/*jslint + es5: true, evil: true, nomen: true, regexp: true, todo: true +*/ + +// The current option set is + +// anon true, if the space may be omitted in anonymous function declarations +// bitwise true, if bitwise operators should be allowed +// browser true, if the standard browser globals should be predefined +// 'continue' true, if the continuation statement should be tolerated +// css true, if CSS workarounds should be tolerated +// debug true, if debugger statements should be allowed +// devel true, if logging should be allowed (console, alert, etc.) +// eqeq true, if == should be allowed +// es5 true, if ES5 syntax should be allowed +// evil true, if eval should be allowed +// forin true, if for in statements need not filter +// fragment true, if HTML fragments should be allowed +// indent the indentation factor +// maxerr the maximum number of errors to allow +// maxlen the maximum length of a source line +// newcap true, if constructor names capitalization is ignored +// node true, if Node.js globals should be predefined +// nomen true, if names may have dangling _ +// on true, if HTML event handlers should be allowed +// passfail true, if the scan should stop on first error +// plusplus true, if increment/decrement should be allowed +// properties true, if all property names must be declared with /*properties*/ +// regexp true, if the . should be allowed in regexp literals +// rhino true, if the Rhino environment globals should be predefined +// undef true, if variables can be declared out of order +// unparam true, if unused parameters should be tolerated +// sloppy true, if the 'use strict'; pragma is optional +// stupid true, if really stupid practices are tolerated +// sub true, if all forms of subscript notation are tolerated +// todo true, if TODO comments are tolerated +// vars true, if multiple var statements per function should be allowed +// white true, if sloppy whitespace is tolerated +// windows true, if MS Windows-specific globals should be predefined + +// The properties directive declares an exclusive list of property names. +// Any properties named in the program that are not in the list will +// produce a warning. + +// For example: + +/*properties + '\b', '\t', '\n', '\f', '\r', '!', '!=', '!==', '"', '%', '\'', + '(arguments)', '(begin)', '(breakage)', '(context)', '(error)', + '(identifier)', '(level)', '(line)', '(loopage)', '(name)', '(params)', + '(scope)', '(token)', '(vars)', '(verb)', '*', '+', '-', '/', '<', '<=', + '==', '===', '>', '>=', ADSAFE, Array, Date, Object, '\\', a, a_label, + a_not_allowed, a_not_defined, a_scope, abbr, acronym, address, adsafe, + adsafe_a, adsafe_autocomplete, adsafe_bad_id, adsafe_div, adsafe_fragment, + adsafe_go, adsafe_html, adsafe_id, adsafe_id_go, adsafe_lib, + adsafe_lib_second, adsafe_missing_id, adsafe_name_a, adsafe_placement, + adsafe_prefix_a, adsafe_script, adsafe_source, adsafe_subscript_a, + adsafe_tag, all, already_defined, and, anon, applet, apply, approved, area, + arity, article, aside, assign, assign_exception, + assignment_function_expression, at, attribute_case_a, audio, autocomplete, + avoid_a, b, background, 'background-attachment', 'background-color', + 'background-image', 'background-position', 'background-repeat', + bad_assignment, bad_color_a, bad_constructor, bad_entity, bad_html, + bad_id_a, bad_in_a, bad_invocation, bad_name_a, bad_new, bad_number, + bad_operand, bad_style, bad_type, bad_url_a, bad_wrap, base, bdo, big, + bitwise, block, blockquote, body, border, 'border-bottom', + 'border-bottom-color', 'border-bottom-left-radius', + 'border-bottom-right-radius', 'border-bottom-style', 'border-bottom-width', + 'border-collapse', 'border-color', 'border-left', 'border-left-color', + 'border-left-style', 'border-left-width', 'border-radius', 'border-right', + 'border-right-color', 'border-right-style', 'border-right-width', + 'border-spacing', 'border-style', 'border-top', 'border-top-color', + 'border-top-left-radius', 'border-top-right-radius', 'border-top-style', + 'border-top-width', 'border-width', bottom, 'box-shadow', br, braille, + browser, button, c, call, canvas, caption, 'caption-side', center, charAt, + charCodeAt, character, cite, clear, clip, closure, cm, code, col, colgroup, + color, combine_var, command, conditional_assignment, confusing_a, + confusing_regexp, constructor_name_a, content, continue, control_a, + 'counter-increment', 'counter-reset', create, css, cursor, d, + dangerous_comment, dangling_a, data, datalist, dd, debug, del, deleted, + details, devel, dfn, dialog, dir, direction, display, disrupt, div, dl, dt, + duplicate_a, edge, edition, else, em, embed, embossed, empty, 'empty-cells', + empty_block, empty_case, empty_class, entityify, eqeq, error_report, errors, + es5, eval, evidence, evil, ex, exception, exec, expected_a, + expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d, expected_at_a, + expected_attribute_a, expected_attribute_value_a, expected_class_a, + expected_fraction_a, expected_id_a, expected_identifier_a, + expected_identifier_a_reserved, expected_lang_a, expected_linear_a, + expected_media_a, expected_name_a, expected_nonstandard_style_attribute, + expected_number_a, expected_operator_a, expected_percent_a, + expected_positive_a, expected_pseudo_a, expected_selector_a, + expected_small_a, expected_space_a_b, expected_string_a, + expected_style_attribute, expected_style_pattern, expected_tagname_a, + expected_type_a, f, fieldset, figcaption, figure, filter, first, flag, + float, floor, font, 'font-family', 'font-size', 'font-size-adjust', + 'font-stretch', 'font-style', 'font-variant', 'font-weight', footer, + forEach, for_if, forin, form, fragment, frame, frameset, from, fromCharCode, + fud, function, function_block, function_eval, function_loop, + function_statement, function_strict, functions, global, globals, h1, h2, h3, + h4, h5, h6, handheld, hasOwnProperty, head, header, height, hgroup, hr, + 'hta:application', html, html_confusion_a, html_handlers, i, id, identifier, + identifier_function, iframe, img, immed, implied_evil, in, indent, indexOf, + infix_in, init, input, ins, insecure_a, isAlpha, isArray, isDigit, isNaN, + join, jslint, json, kbd, keygen, keys, label, labeled, lang, lbp, + leading_decimal_a, led, left, legend, length, 'letter-spacing', level, li, + lib, line, 'line-height', link, 'list-style', 'list-style-image', + 'list-style-position', 'list-style-type', map, margin, 'margin-bottom', + 'margin-left', 'margin-right', 'margin-top', mark, 'marker-offset', match, + 'max-height', 'max-width', maxerr, maxlen, menu, message, meta, meter, + 'min-height', 'min-width', missing_a, missing_a_after_b, missing_option, + missing_property, missing_space_a_b, missing_url, missing_use_strict, mm, + mode, move_invocation, move_var, n, name, name_function, nav, + nested_comment, newcap, node, noframes, nomen, noscript, not, + not_a_constructor, not_a_defined, not_a_function, not_a_label, not_a_scope, + not_greater, nud, number, object, octal_a, ol, on, opacity, open, optgroup, + option, outer, outline, 'outline-color', 'outline-style', 'outline-width', + output, overflow, 'overflow-x', 'overflow-y', p, padding, 'padding-bottom', + 'padding-left', 'padding-right', 'padding-top', 'page-break-after', + 'page-break-before', param, parameter_a_get_b, parameter_arguments_a, + parameter_set_a, params, paren, parent, passfail, pc, plusplus, pop, + position, postscript, pre, predef, print, progress, projection, properties, + properties_report, property, prototype, pt, push, px, q, quote, quotes, r, + radix, range, raw, read_only, reason, redefinition_a, regexp, replace, + report, reserved, reserved_a, rhino, right, rp, rt, ruby, safe, samp, + scanned_a_b, screen, script, search, second, section, select, shift, + slash_equal, slice, sloppy, small, sort, source, span, speech, split, src, + statement_block, stopping, strange_loop, strict, string, strong, stupid, + style, styleproperty, sub, subscript, substr, summary, sup, supplant, + sync_a, t, table, 'table-layout', tag_a_in_b, tbody, td, test, 'text-align', + 'text-decoration', 'text-indent', 'text-shadow', 'text-transform', textarea, + tfoot, th, thead, third, thru, time, title, toLowerCase, toString, + toUpperCase, todo, todo_comment, token, tokens, too_long, too_many, top, tr, + trailing_decimal_a, tree, tt, tty, tv, type, u, ul, unclosed, + unclosed_comment, unclosed_regexp, undef, undefined, unescaped_a, + unexpected_a, unexpected_char_a_b, unexpected_comment, unexpected_else, + unexpected_label_a, unexpected_property_a, unexpected_space_a_b, + unexpected_typeof_a, 'unicode-bidi', unnecessary_initialize, + unnecessary_use, unparam, unreachable_a_b, unrecognized_style_attribute_a, + unrecognized_tag_a, unsafe, unused, url, urls, use_array, use_braces, + use_charAt, use_object, use_or, use_param, use_spaces, used_before_a, var, + var_a_not, vars, 'vertical-align', video, visibility, was, weird_assignment, + weird_condition, weird_new, weird_program, weird_relation, weird_ternary, + white, 'white-space', width, windows, 'word-spacing', 'word-wrap', wrap, + wrap_immediate, wrap_regexp, write_is_wrong, writeable, 'z-index' +*/ + +// The global directive is used to declare global variables that can +// be accessed by the program. If a declaration is true, then the variable +// is writeable. Otherwise, it is read-only. + +// 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 JSLINT function itself. That function is also an object that +// can contain data and other functions. + +var JSLINT = (function () { + 'use strict'; + + function array_to_object(array, value) { + +// Make an object from an array of keys and a common value. + + var i, length = array.length, object = {}; + for (i = 0; i < length; i += 1) { + object[array[i]] = value; + } + return object; + } + + + var adsafe_id, // The widget's ADsafe id. + adsafe_may, // The widget may load approved scripts. + adsafe_top, // At the top of the widget script. + adsafe_went, // ADSAFE.go has been called. + allowed_option = { + anon : true, + bitwise : true, + browser : true, + 'continue': true, + css : true, + debug : true, + devel : true, + eqeq : true, + es5 : true, + evil : true, + forin : true, + fragment : true, + indent : 10, + maxerr : 1000, + maxlen : 256, + newcap : true, + node : true, + nomen : true, + on : true, + passfail : true, + plusplus : true, + properties: true, + regexp : true, + rhino : true, + undef : true, + unparam : true, + sloppy : true, + stupid : true, + sub : true, + todo : true, + vars : true, + white : true, + windows : true + }, + anonname, // The guessed name for anonymous functions. + approved, // ADsafe approved urls. + +// 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 property names that should not be permitted in the safe subset. + + banned = array_to_object([ + 'arguments', 'callee', 'caller', 'constructor', 'eval', 'prototype', + 'stack', 'unwatch', 'valueOf', 'watch' + ], true), + begin, // The root token + +// browser contains a set of global names that are commonly provided by a +// web browser environment. + + browser = array_to_object([ + 'clearInterval', 'clearTimeout', 'document', 'event', 'FormData', + 'frames', 'history', 'Image', 'localStorage', 'location', 'name', + 'navigator', 'Option', 'parent', 'screen', 'sessionStorage', + 'setInterval', 'setTimeout', 'Storage', 'window', 'XMLHttpRequest' + ], false), + +// bundle contains the text messages. + + bundle = { + a_label: "'{a}' is a statement label.", + a_not_allowed: "'{a}' is not allowed.", + a_not_defined: "'{a}' is not defined.", + a_scope: "'{a}' used out of scope.", + adsafe_a: "ADsafe violation: '{a}'.", + adsafe_autocomplete: "ADsafe autocomplete violation.", + adsafe_bad_id: "ADSAFE violation: bad id.", + adsafe_div: "ADsafe violation: Wrap the widget in a div.", + adsafe_fragment: "ADSAFE: Use the fragment option.", + adsafe_go: "ADsafe violation: Misformed ADSAFE.go.", + adsafe_html: "Currently, ADsafe does not operate on whole HTML " + + "documents. It operates on
    fragments and .js files.", + adsafe_id: "ADsafe violation: id does not match.", + adsafe_id_go: "ADsafe violation: Missing ADSAFE.id or ADSAFE.go.", + adsafe_lib: "ADsafe lib violation.", + adsafe_lib_second: "ADsafe: The second argument to lib must be a function.", + adsafe_missing_id: "ADSAFE violation: missing ID_.", + adsafe_name_a: "ADsafe name violation: '{a}'.", + adsafe_placement: "ADsafe script placement violation.", + adsafe_prefix_a: "ADsafe violation: An id must have a '{a}' prefix", + adsafe_script: "ADsafe script violation.", + adsafe_source: "ADsafe unapproved script source.", + adsafe_subscript_a: "ADsafe subscript '{a}'.", + adsafe_tag: "ADsafe violation: Disallowed tag '{a}'.", + already_defined: "'{a}' is already defined.", + and: "The '&&' subexpression should be wrapped in parens.", + assign_exception: "Do not assign to the exception parameter.", + assignment_function_expression: "Expected an assignment or " + + "function call and instead saw an expression.", + attribute_case_a: "Attribute '{a}' not all lower case.", + avoid_a: "Avoid '{a}'.", + bad_assignment: "Bad assignment.", + bad_color_a: "Bad hex color '{a}'.", + bad_constructor: "Bad constructor.", + bad_entity: "Bad entity.", + bad_html: "Bad HTML string", + bad_id_a: "Bad id: '{a}'.", + bad_in_a: "Bad for in variable '{a}'.", + bad_invocation: "Bad invocation.", + bad_name_a: "Bad name: '{a}'.", + bad_new: "Do not use 'new' for side effects.", + bad_number: "Bad number '{a}'.", + bad_operand: "Bad operand.", + bad_style: "Bad style.", + bad_type: "Bad type.", + bad_url_a: "Bad url '{a}'.", + bad_wrap: "Do not wrap function literals in parens unless they " + + "are to be immediately invoked.", + combine_var: "Combine this with the previous 'var' statement.", + conditional_assignment: "Expected a conditional expression and " + + "instead saw an assignment.", + confusing_a: "Confusing use of '{a}'.", + confusing_regexp: "Confusing regular expression.", + constructor_name_a: "A constructor name '{a}' should start with " + + "an uppercase letter.", + control_a: "Unexpected control character '{a}'.", + css: "A css file should begin with @charset 'UTF-8';", + dangling_a: "Unexpected dangling '_' in '{a}'.", + dangerous_comment: "Dangerous comment.", + deleted: "Only properties should be deleted.", + duplicate_a: "Duplicate '{a}'.", + empty_block: "Empty block.", + empty_case: "Empty case.", + empty_class: "Empty class.", + es5: "This is an ES5 feature.", + evil: "eval is evil.", + expected_a: "Expected '{a}'.", + expected_a_b: "Expected '{a}' and instead saw '{b}'.", + expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line " + + "{c} and instead saw '{d}'.", + expected_at_a: "Expected an at-rule, and instead saw @{a}.", + expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.", + expected_attribute_a: "Expected an attribute, and instead saw [{a}].", + expected_attribute_value_a: "Expected an attribute value and " + + "instead saw '{a}'.", + expected_class_a: "Expected a class, and instead saw .{a}.", + expected_fraction_a: "Expected a number between 0 and 1 and " + + "instead saw '{a}'", + expected_id_a: "Expected an id, and instead saw #{a}.", + expected_identifier_a: "Expected an identifier and instead saw '{a}'.", + expected_identifier_a_reserved: "Expected an identifier and " + + "instead saw '{a}' (a reserved word).", + expected_linear_a: "Expected a linear unit and instead saw '{a}'.", + expected_lang_a: "Expected a lang code, and instead saw :{a}.", + expected_media_a: "Expected a CSS media type, and instead saw '{a}'.", + expected_name_a: "Expected a name and instead saw '{a}'.", + expected_nonstandard_style_attribute: "Expected a non-standard " + + "style attribute and instead saw '{a}'.", + expected_number_a: "Expected a number and instead saw '{a}'.", + expected_operator_a: "Expected an operator and instead saw '{a}'.", + expected_percent_a: "Expected a percentage and instead saw '{a}'", + expected_positive_a: "Expected a positive number and instead saw '{a}'", + expected_pseudo_a: "Expected a pseudo, and instead saw :{a}.", + expected_selector_a: "Expected a CSS selector, and instead saw {a}.", + expected_small_a: "Expected a small positive integer and instead saw '{a}'", + expected_space_a_b: "Expected exactly one space between '{a}' and '{b}'.", + expected_string_a: "Expected a string and instead saw '{a}'.", + expected_style_attribute: "Excepted a style attribute, and instead saw '{a}'.", + expected_style_pattern: "Expected a style pattern, and instead saw '{a}'.", + expected_tagname_a: "Expected a tagName, and instead saw {a}.", + expected_type_a: "Expected a type, and instead saw {a}.", + for_if: "The body of a for in should be wrapped in an if " + + "statement to filter unwanted properties from the prototype.", + function_block: "Function statements should not be placed in blocks." + + "Use a function expression or move the statement to the top of " + + "the outer function.", + function_eval: "The Function constructor is eval.", + function_loop: "Don't make functions within a loop.", + function_statement: "Function statements are not invocable." + + "Wrap the whole function invocation in parens.", + function_strict: "Use the function form of 'use strict'.", + html_confusion_a: "HTML confusion in regular expression '<{a}'.", + html_handlers: "Avoid HTML event handlers.", + identifier_function: "Expected an identifier in an assignment " + + "and instead saw a function invocation.", + implied_evil: "Implied eval is evil. Pass a function instead of a string.", + infix_in: "Unexpected 'in'. Compare with undefined, or use the " + + "hasOwnProperty method instead.", + insecure_a: "Insecure '{a}'.", + isNaN: "Use the isNaN function to compare with NaN.", + lang: "lang is deprecated.", + leading_decimal_a: "A leading decimal point can be confused with a dot: '.{a}'.", + missing_a: "Missing '{a}'.", + missing_a_after_b: "Missing '{a}' after '{b}'.", + missing_option: "Missing option value.", + missing_property: "Missing property name.", + missing_space_a_b: "Missing space between '{a}' and '{b}'.", + missing_url: "Missing url.", + missing_use_strict: "Missing 'use strict' statement.", + move_invocation: "Move the invocation into the parens that " + + "contain the function.", + move_var: "Move 'var' declarations to the top of the function.", + name_function: "Missing name in function statement.", + nested_comment: "Nested comment.", + not: "Nested not.", + not_a_constructor: "Do not use {a} as a constructor.", + not_a_defined: "'{a}' has not been fully defined yet.", + not_a_function: "'{a}' is not a function.", + not_a_label: "'{a}' is not a label.", + not_a_scope: "'{a}' is out of scope.", + not_greater: "'{a}' should not be greater than '{b}'.", + octal_a: "Don't use octal: '{a}'. Use '\\u....' instead.", + parameter_arguments_a: "Do not mutate parameter '{a}' when using 'arguments'.", + parameter_a_get_b: "Unexpected parameter '{a}' in get {b} function.", + parameter_set_a: "Expected parameter (value) in set {a} function.", + radix: "Missing radix parameter.", + read_only: "Read only.", + redefinition_a: "Redefinition of '{a}'.", + reserved_a: "Reserved name '{a}'.", + scanned_a_b: "{a} ({b}% scanned).", + slash_equal: "A regular expression literal can be confused with '/='.", + statement_block: "Expected to see a statement and instead saw a block.", + stopping: "Stopping.", + strange_loop: "Strange loop.", + strict: "Strict violation.", + subscript: "['{a}'] is better written in dot notation.", + sync_a: "Unexpected sync method: '{a}'.", + tag_a_in_b: "A '<{a}>' must be within '<{b}>'.", + todo_comment: "Unexpected TODO comment.", + too_long: "Line too long.", + too_many: "Too many errors.", + trailing_decimal_a: "A trailing decimal point can be confused " + + "with a dot: '.{a}'.", + type: "type is unnecessary.", + unclosed: "Unclosed string.", + unclosed_comment: "Unclosed comment.", + unclosed_regexp: "Unclosed regular expression.", + unescaped_a: "Unescaped '{a}'.", + unexpected_a: "Unexpected '{a}'.", + unexpected_char_a_b: "Unexpected character '{a}' in {b}.", + unexpected_comment: "Unexpected comment.", + unexpected_else: "Unexpected 'else' after 'return'.", + unexpected_label_a: "Unexpected label '{a}'.", + unexpected_property_a: "Unexpected /*property*/ '{a}'.", + unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.", + unexpected_typeof_a: "Unexpected 'typeof'. " + + "Use '===' to compare directly with {a}.", + unnecessary_initialize: "It is not necessary to initialize '{a}' " + + "to 'undefined'.", + unnecessary_use: "Unnecessary 'use strict'.", + unreachable_a_b: "Unreachable '{a}' after '{b}'.", + unrecognized_style_attribute_a: "Unrecognized style attribute '{a}'.", + unrecognized_tag_a: "Unrecognized tag '<{a}>'.", + unsafe: "Unsafe character.", + url: "JavaScript URL.", + use_array: "Use the array literal notation [].", + use_braces: "Spaces are hard to count. Use {{a}}.", + use_charAt: "Use the charAt method.", + use_object: "Use the object literal notation {}.", + use_or: "Use the || operator.", + use_param: "Use a named parameter.", + use_spaces: "Use spaces, not tabs.", + used_before_a: "'{a}' was used before it was defined.", + var_a_not: "Variable {a} was not declared correctly.", + weird_assignment: "Weird assignment.", + weird_condition: "Weird condition.", + weird_new: "Weird construction. Delete 'new'.", + weird_program: "Weird program.", + weird_relation: "Weird relation.", + weird_ternary: "Weird ternary.", + wrap_immediate: "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.", + wrap_regexp: "Wrap the /regexp/ literal in parens to " + + "disambiguate the slash operator.", + write_is_wrong: "document.write can be a form of eval." + }, + comments_off, + css_attribute_data, + css_any, + + css_colorData = array_to_object([ + "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", + "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", + "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", + "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", + "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkkhaki", + "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", + "darkred", "darksalmon", "darkseagreen", "darkslateblue", + "darkslategray", "darkturquoise", "darkviolet", "deeppink", + "deepskyblue", "dimgray", "dodgerblue", "firebrick", "floralwhite", + "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", + "goldenrod", "gray", "green", "greenyellow", "honeydew", "hotpink", + "indianred", "indigo", "ivory", "khaki", "lavender", + "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", + "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgreen", + "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", + "lightslategray", "lightsteelblue", "lightyellow", "lime", + "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", + "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", + "mediumslateblue", "mediumspringgreen", "mediumturquoise", + "mediumvioletred", "midnightblue", "mintcream", "mistyrose", + "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", + "orange", "orangered", "orchid", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", + "peru", "pink", "plum", "powderblue", "purple", "red", "rosybrown", + "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", + "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", + "snow", "springgreen", "steelblue", "tan", "teal", "thistle", + "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", + "yellow", "yellowgreen", + + "activeborder", "activecaption", "appworkspace", "background", + "buttonface", "buttonhighlight", "buttonshadow", "buttontext", + "captiontext", "graytext", "highlight", "highlighttext", + "inactiveborder", "inactivecaption", "inactivecaptiontext", + "infobackground", "infotext", "menu", "menutext", "scrollbar", + "threeddarkshadow", "threedface", "threedhighlight", + "threedlightshadow", "threedshadow", "window", "windowframe", + "windowtext" + ], true), + + css_border_style, + css_break, + + css_lengthData = { + '%': true, + 'cm': true, + 'em': true, + 'ex': true, + 'in': true, + 'mm': true, + 'pc': true, + 'pt': true, + 'px': true + }, + + css_media, + css_overflow, + + descapes = { + 'b': '\b', + 't': '\t', + 'n': '\n', + 'f': '\f', + 'r': '\r', + '"': '"', + '/': '/', + '\\': '\\', + '!': '!' + }, + + devel = array_to_object([ + 'alert', 'confirm', 'console', 'Debug', 'opera', 'prompt', 'WSH' + ], false), + directive, + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\'': '\\\'', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function, including the labels used in + // the function, as well as (breakage), + // (context), (loopage), (name), (params), (token), + // (vars), (verb) + + functionicity = [ + 'closure', 'exception', 'global', 'label', 'outer', 'undef', + 'unused', 'var' + ], + + functions, // All of the functions + global_funct, // The global body + global_scope, // The global scope + html_tag = { + a: {}, + abbr: {}, + acronym: {}, + address: {}, + applet: {}, + area: {empty: true, parent: ' map '}, + article: {}, + aside: {}, + audio: {}, + b: {}, + base: {empty: true, parent: ' head '}, + bdo: {}, + big: {}, + blockquote: {}, + body: {parent: ' html noframes '}, + br: {empty: true}, + button: {}, + canvas: {parent: ' body p div th td '}, + caption: {parent: ' table '}, + center: {}, + cite: {}, + code: {}, + col: {empty: true, parent: ' table colgroup '}, + colgroup: {parent: ' table '}, + command: {parent: ' menu '}, + datalist: {}, + dd: {parent: ' dl '}, + del: {}, + details: {}, + dialog: {}, + dfn: {}, + dir: {}, + div: {}, + dl: {}, + dt: {parent: ' dl '}, + em: {}, + embed: {}, + fieldset: {}, + figcaption: {parent: ' figure '}, + figure: {}, + font: {}, + footer: {}, + form: {}, + frame: {empty: true, parent: ' frameset '}, + frameset: {parent: ' html frameset '}, + h1: {}, + h2: {}, + h3: {}, + h4: {}, + h5: {}, + h6: {}, + head: {parent: ' html '}, + header: {}, + hgroup: {}, + hr: {empty: true}, + 'hta:application': + {empty: true, parent: ' head '}, + html: {parent: '*'}, + i: {}, + iframe: {}, + img: {empty: true}, + input: {empty: true}, + ins: {}, + kbd: {}, + keygen: {}, + label: {}, + legend: {parent: ' details fieldset figure '}, + li: {parent: ' dir menu ol ul '}, + link: {empty: true, parent: ' head '}, + map: {}, + mark: {}, + menu: {}, + meta: {empty: true, parent: ' head noframes noscript '}, + meter: {}, + nav: {}, + noframes: {parent: ' html body '}, + noscript: {parent: ' body head noframes '}, + object: {}, + ol: {}, + optgroup: {parent: ' select '}, + option: {parent: ' optgroup select '}, + output: {}, + p: {}, + param: {empty: true, parent: ' applet object '}, + pre: {}, + progress: {}, + q: {}, + rp: {}, + rt: {}, + ruby: {}, + samp: {}, + script: {empty: true, parent: ' body div frame head iframe p pre span '}, + section: {}, + select: {}, + small: {}, + span: {}, + source: {}, + strong: {}, + style: {parent: ' head ', empty: true}, + sub: {}, + summary: {parent: ' details '}, + sup: {}, + table: {}, + tbody: {parent: ' table '}, + td: {parent: ' tr '}, + textarea: {}, + tfoot: {parent: ' table '}, + th: {parent: ' tr '}, + thead: {parent: ' table '}, + time: {}, + title: {parent: ' head '}, + tr: {parent: ' table tbody thead tfoot '}, + tt: {}, + u: {}, + ul: {}, + 'var': {}, + video: {} + }, + + ids, // HTML ids + in_block, + indent, + itself, // JSLint itself + json_mode, + lex, // the tokenizer + lines, + lookahead, + node = array_to_object([ + 'Buffer', 'clearInterval', 'clearTimeout', 'console', 'exports', + 'global', 'module', 'process', 'querystring', 'require', + 'setInterval', 'setTimeout', '__dirname', '__filename' + ], false), + node_js, + numbery = array_to_object(['indexOf', 'lastIndexOf', 'search'], true), + next_token, + option, + predefined, // Global variables defined by option + prereg, + prev_token, + property, + regexp_flag = array_to_object(['g', 'i', 'm'], true), + return_this = function return_this() { + return this; + }, + rhino = array_to_object([ + 'defineClass', 'deserialize', 'gc', 'help', 'load', 'loadClass', + 'print', 'quit', 'readFile', 'readUrl', 'runCommand', 'seal', + 'serialize', 'spawn', 'sync', 'toint32', 'version' + ], false), + + scope, // An object containing an object for each variable in scope + semicolon_coda = array_to_object([';', '"', '\'', ')'], true), + src, + stack, + +// standard contains the global names that are provided by the +// ECMAScript standard. + + standard = array_to_object([ + 'Array', 'Boolean', 'Date', 'decodeURI', 'decodeURIComponent', + 'encodeURI', 'encodeURIComponent', 'Error', 'eval', 'EvalError', + 'Function', 'isFinite', 'isNaN', 'JSON', 'Math', 'Number', + 'Object', 'parseInt', 'parseFloat', 'RangeError', 'ReferenceError', + 'RegExp', 'String', 'SyntaxError', 'TypeError', 'URIError' + ], false), + + strict_mode, + syntax = {}, + tab, + token, + tokens, + urls, + var_mode, + warnings, + + windows = array_to_object([ + 'ActiveXObject', 'CScript', 'Debug', 'Enumerator', 'System', + 'VBArray', 'WScript', 'WSH' + ], false), + +// xmode is used to adapt to the exceptions in html parsing. +// It can have these states: +// '' .js script file +// 'html' +// 'outer' +// 'script' +// 'style' +// 'scriptstring' +// 'styleproperty' + + xmode, + xquote, + +// Regular expressions. Some of these are stupidly long. + +// unsafe comment or string + ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i, +// carriage return, carriage return linefeed, or linefeed + crlfx = /\r\n?|\n/, +// unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, +// query characters for ids + dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, +// html token + hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, +// identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, +// javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, +// star slash + lx = /\*\/|\/\*/, +// 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]/g, +// outer html token + ox = /[>&]|<[\/!]?|--/, +// attributes characters + qx = /[^a-zA-Z0-9+\-_\/. ]/, +// style + ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, + sx = /^\s*([{}:#%.=,>+\[\]@()"';]|[*$\^~]=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, + +// sync + syx = /Sync$/, +// comment todo + tox = /^\W*to\s*do(?:\W|$)/i, +// token + tx = /^\s*([(){}\[\]\?.,:;'"~#@`]|={1,3}|\/(\*(jslint|properties|property|members?|globals?)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<(?:[\/=!]|\!(\[|--)?|<=?)?|\!(\!|==?)?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+(?:[xX][0-9a-fA-F]+|\.[0-9]*)?(?:[eE][+\-]?[0-9]+)?)/, +// url badness + ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto|script/i, + + rx = { + outer: hx, + html: hx, + style: sx, + styleproperty: ssx + }; + + + function F() {} // Used by Object.create + +// Provide critical ES5 functions to ES3. + + if (typeof Array.prototype.filter !== 'function') { + Array.prototype.filter = function (f) { + var i, length = this.length, result = [], value; + for (i = 0; i < length; i += 1) { + try { + value = this[i]; + if (f(value)) { + result.push(value); + } + } catch (ignore) { + } + } + return result; + }; + } + + if (typeof Array.prototype.forEach !== 'function') { + Array.prototype.forEach = function (f) { + var i, length = this.length; + for (i = 0; i < length; i += 1) { + try { + f(this[i]); + } catch (ignore) { + } + } + }; + } + + if (typeof Array.isArray !== 'function') { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + }; + } + + if (!Object.prototype.hasOwnProperty.call(Object, 'create')) { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== 'function') { + Object.keys = function (o) { + var array = [], key; + for (key in o) { + if (Object.prototype.hasOwnProperty.call(o, key)) { + array.push(key); + } + } + return array; + }; + } + + 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 replacement = o[b]; + return typeof replacement === 'string' || + typeof replacement === 'number' ? replacement : a; + }); + }; + } + + + function sanitize(a) { + +// Escapify a troublesome character. + + return escapes[a] || + '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + } + + + function add_to_predefined(group) { + Object.keys(group).forEach(function (name) { + predefined[name] = group[name]; + }); + } + + + function assume() { + if (!option.safe) { + if (option.rhino) { + add_to_predefined(rhino); + option.rhino = false; + } + if (option.devel) { + add_to_predefined(devel); + option.devel = false; + } + if (option.browser) { + add_to_predefined(browser); + option.browser = false; + } + if (option.windows) { + add_to_predefined(windows); + option.windows = false; + } + if (option.node) { + add_to_predefined(node); + option.node = false; + node_js = true; + } + } + } + + +// Produce an error warning. + + function artifact(tok) { + if (!tok) { + tok = next_token; + } + return tok.number || tok.string; + } + + function quit(message, line, character) { + throw { + name: 'JSLintError', + line: line, + character: character, + message: bundle.scanned_a_b.supplant({ + a: message, + b: Math.floor((line / lines.length) * 100) + }) + }; + } + + function warn(message, offender, a, b, c, d) { + var character, line, warning; + offender = offender || next_token; // ~~ + line = offender.line || 0; + character = offender.from || 0; + warning = { + id: '(error)', + raw: bundle[message] || message, + evidence: lines[line - 1] || '', + line: line, + character: character, + a: a || (offender.id === '(number)' + ? String(offender.number) + : offender.string), + b: b, + c: c, + d: d + }; + warning.reason = warning.raw.supplant(warning); + JSLINT.errors.push(warning); + if (option.passfail) { + quit(bundle.stopping, line, character); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit(bundle.too_many, line, character); + } + return warning; + } + + function warn_at(message, line, character, a, b, c, d) { + return warn(message, { + line: line, + from: character + }, a, b, c, d); + } + + function stop(message, offender, a, b, c, d) { + var warning = warn(message, offender, a, b, c, d); + quit(bundle.stopping, warning.line, warning.character); + } + + function stop_at(message, line, character, a, b, c, d) { + return stop(message, { + line: line, + from: character + }, a, b, c, d); + } + + function expected_at(at) { + if (!option.white && next_token.from !== at) { + warn('expected_a_at_b_c', next_token, '', at, + next_token.from); + } + } + + function aint(it, name, expected) { + if (it[name] !== expected) { + warn('expected_a_b', it, expected, it[name]); + return true; + } + return false; + } + + +// lexical analysis and token construction + + lex = (function lex() { + var character, c, from, length, line, pos, source_row; + +// Private lex methods + + function next_line() { + var at; + character = 1; + source_row = lines[line]; + line += 1; + if (source_row === undefined) { + return false; + } + at = source_row.search(/\t/); + if (at >= 0) { + if (option.white) { + source_row = source_row.replace(/\t/g, ' '); + } else { + warn_at('use_spaces', line, at + 1); + } + } + at = source_row.search(cx); + if (at >= 0) { + warn_at('unsafe', line, at); + } + if (option.maxlen && option.maxlen < source_row.length) { + warn_at('too_long', line, source_row.length); + } + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var id, the_token; + if (type === '(string)' || type === '(range)') { + if (jx.test(value)) { + warn_at('url', line, from); + } + } + the_token = Object.create(syntax[( + type === '(punctuator)' || (type === '(identifier)' && + Object.prototype.hasOwnProperty.call(syntax, value)) + ? value + : type + )] || syntax['(error)']); + if (type === '(identifier)') { + the_token.identifier = true; + if (value === '__iterator__' || value === '__proto__') { + stop_at('reserved_a', line, from, value); + } else if (!option.nomen && + (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + warn_at('dangling_a', line, from, value); + } + } + if (type === '(number)') { + the_token.number = +value; + } else if (value !== undefined) { + the_token.string = String(value); + } + the_token.line = line; + the_token.from = from; + the_token.thru = character; + id = the_token.id; + prereg = id && ( + ('(,=:[!&|?{};~+-*%^<>'.indexOf(id.charAt(id.length - 1)) >= 0) || + id === 'return' || id === 'case' + ); + return the_token; + } + + function match(x) { + var exec = x.exec(source_row), first; + if (exec) { + length = exec[0].length; + first = exec[1]; + c = first.charAt(0); + source_row = source_row.slice(length); + from = character + length - first.length; + character += length; + return first; + } + } + + function string(x) { + var c, pos = 0, r = '', result; + + function hex(n) { + var i = parseInt(source_row.substr(pos + 1, n), 16); + pos += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warn_at('unexpected_a', line, character, '\\'); + } + character += n; + c = String.fromCharCode(i); + } + + if (json_mode && x !== '"') { + warn_at('expected_a', line, character, '"'); + } + + if (xquote === x || (xmode === 'scriptstring' && !xquote)) { + return it('(punctuator)', x); + } + + for (;;) { + while (pos >= source_row.length) { + pos = 0; + if (xmode !== 'html' || !next_line()) { + stop_at('unclosed', line, from); + } + } + c = source_row.charAt(pos); + if (c === x) { + character += 1; + source_row = source_row.slice(pos + 1); + result = it('(string)', r); + result.quote = x; + return result; + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warn_at('control_a', line, character + pos, + source_row.slice(0, pos)); + } else if (c === xquote) { + warn_at('bad_html', line, character + pos); + } else if (c === '<') { + if (option.safe && xmode === 'html') { + warn_at('adsafe_a', line, character + pos, c); + } else if (source_row.charAt(pos + 1) === '/' && (xmode || option.safe)) { + warn_at('expected_a_b', line, character, + '<\\/', '= '0' && c <= '7' ? 'octal_a' : 'unexpected_a', + line, character, '\\' + c); + } else { + c = descapes[c]; + } + } + } + } + r += c; + character += 1; + pos += 1; + } + } + + function number(snippet) { + var digit; + if (xmode !== 'style' && xmode !== 'styleproperty' && + source_row.charAt(0).isAlpha()) { + warn_at('expected_space_a_b', + line, character, c, source_row.charAt(0)); + } + if (c === '0') { + digit = snippet.charAt(1); + if (digit.isDigit()) { + if (token.id !== '.' && xmode !== 'styleproperty') { + warn_at('unexpected_a', line, character, snippet); + } + } else if (json_mode && (digit === 'x' || digit === 'X')) { + warn_at('unexpected_a', line, character, '0x'); + } + } + if (snippet.slice(snippet.length - 1) === '.') { + warn_at('trailing_decimal_a', line, character, snippet); + } + if (xmode !== 'style') { + digit = +snippet; + if (!isFinite(digit)) { + warn_at('bad_number', line, character, snippet); + } + snippet = digit; + } + return it('(number)', snippet); + } + + function comment(snippet) { + if (comments_off || src || (xmode && xmode !== 'script' && + xmode !== 'style' && xmode !== 'styleproperty')) { + warn_at('unexpected_comment', line, character); + } else if (xmode === 'script' && /<\//i.test(source_row)) { + warn_at('unexpected_a', line, character, '<\/'); + } else if (option.safe && ax.test(snippet)) { + warn_at('dangerous_comment', line, character); + } else if (!option.todo && tox.test(snippet)) { + warn_at('todo_comment', line, character); + } + } + + function regexp() { + var b, + bit, + captures = 0, + depth = 0, + flag = '', + high, + letter, + length = 0, + low, + potential, + quote, + result; + for (;;) { + b = true; + c = source_row.charAt(length); + length += 1; + switch (c) { + case '': + stop_at('unclosed_regexp', line, from); + return; + case '/': + if (depth > 0) { + warn_at('unescaped_a', line, from + length, '/'); + } + c = source_row.slice(0, length - 1); + potential = Object.create(regexp_flag); + for (;;) { + letter = source_row.charAt(length); + if (potential[letter] !== true) { + break; + } + potential[letter] = false; + length += 1; + flag += letter; + } + if (source_row.charAt(length).isAlpha()) { + stop_at('unexpected_a', line, from, source_row.charAt(length)); + } + character += length; + source_row = source_row.slice(length); + quote = source_row.charAt(0); + if (quote === '/' || quote === '*') { + stop_at('confusing_regexp', line, from); + } + result = it('(regexp)', c); + result.flag = flag; + return result; + case '\\': + c = source_row.charAt(length); + if (c < ' ') { + warn_at('control_a', line, from + length, String(c)); + } else if (c === '<') { + warn_at(bundle.unexpected_a, line, from + length, '\\'); + } + length += 1; + break; + case '(': + depth += 1; + b = false; + if (source_row.charAt(length) === '?') { + length += 1; + switch (source_row.charAt(length)) { + case ':': + case '=': + case '!': + length += 1; + break; + default: + warn_at(bundle.expected_a_b, line, from + length, + ':', source_row.charAt(length)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warn_at('unescaped_a', line, from + length, ')'); + } else { + depth -= 1; + } + break; + case ' ': + pos = 1; + while (source_row.charAt(length) === ' ') { + length += 1; + pos += 1; + } + if (pos > 1) { + warn_at('use_braces', line, from + length, pos); + } + break; + case '[': + c = source_row.charAt(length); + if (c === '^') { + length += 1; + if (!option.regexp) { + warn_at('insecure_a', line, from + length, c); + } else if (source_row.charAt(length) === ']') { + stop_at('unescaped_a', line, from + length, '^'); + } + } + bit = false; + if (c === ']') { + warn_at('empty_class', line, from + length - 1); + bit = true; + } +klass: do { + c = source_row.charAt(length); + length += 1; + switch (c) { + case '[': + case '^': + warn_at('unescaped_a', line, from + length, c); + bit = true; + break; + case '-': + if (bit) { + bit = false; + } else { + warn_at('unescaped_a', line, from + length, '-'); + bit = true; + } + break; + case ']': + if (!bit) { + warn_at('unescaped_a', line, from + length - 1, '-'); + } + break klass; + case '\\': + c = source_row.charAt(length); + if (c < ' ') { + warn_at(bundle.control_a, line, from + length, String(c)); + } else if (c === '<') { + warn_at(bundle.unexpected_a, line, from + length, '\\'); + } + length += 1; + bit = true; + break; + case '/': + warn_at('unescaped_a', line, from + length - 1, '/'); + bit = true; + break; + case '<': + if (xmode === 'script') { + c = source_row.charAt(length); + if (c === '!' || c === '/') { + warn_at(bundle.html_confusion_a, line, + from + length, c); + } + } + bit = true; + break; + default: + bit = true; + } + } while (c); + break; + case '.': + if (!option.regexp) { + warn_at('insecure_a', line, from + length, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warn_at('unescaped_a', line, from + length, c); + break; + case '<': + if (xmode === 'script') { + c = source_row.charAt(length); + if (c === '!' || c === '/') { + warn_at(bundle.html_confusion_a, line, from + length, c); + } + } + break; + } + if (b) { + switch (source_row.charAt(length)) { + case '?': + case '+': + case '*': + length += 1; + if (source_row.charAt(length) === '?') { + length += 1; + } + break; + case '{': + length += 1; + c = source_row.charAt(length); + if (c < '0' || c > '9') { + warn_at(bundle.expected_number_a, line, + from + length, c); + } + length += 1; + low = +c; + for (;;) { + c = source_row.charAt(length); + if (c < '0' || c > '9') { + break; + } + length += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + length += 1; + high = Infinity; + c = source_row.charAt(length); + if (c >= '0' && c <= '9') { + length += 1; + high = +c; + for (;;) { + c = source_row.charAt(length); + if (c < '0' || c > '9') { + break; + } + length += 1; + high = +c + (high * 10); + } + } + } + if (source_row.charAt(length) !== '}') { + warn_at(bundle.expected_a_b, line, from + length, + '}', c); + } else { + length += 1; + } + if (source_row.charAt(length) === '?') { + length += 1; + } + if (low > high) { + warn_at(bundle.not_greater, line, from + length, + low, high); + } + break; + } + } + } + c = source_row.slice(0, length - 1); + character += length; + source_row = source_row.slice(length); + return it('(regexp)', c); + } + +// Public lex methods + + return { + init: function (source) { + if (typeof source === 'string') { + lines = source.split(crlfx); + } else { + lines = source; + } + line = 0; + next_line(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (source_row.charAt(0) !== begin) { + stop_at('expected_a_b', line, character, begin, + source_row.charAt(0)); + } + for (;;) { + source_row = source_row.slice(1); + character += 1; + c = source_row.charAt(0); + switch (c) { + case '': + stop_at('missing_a', line, character, c); + break; + case end: + source_row = source_row.slice(1); + character += 1; + return it('(range)', value); + case xquote: + case '\\': + warn_at('unexpected_a', line, character, c); + break; + } + value += c; + } + }, + +// token -- this is called by advance to get the next token. + + token: function () { + var c, i, snippet; + + for (;;) { + while (!source_row) { + if (!next_line()) { + return it('(end)'); + } + } + while (xmode === 'outer') { + i = source_row.search(ox); + if (i === 0) { + break; + } else if (i > 0) { + character += i; + source_row = source_row.slice(i); + break; + } else { + if (!next_line()) { + return it('(end)', ''); + } + } + } + snippet = match(rx[xmode] || tx); + if (!snippet) { + if (source_row) { + if (source_row.charAt(0) === ' ') { + if (!option.white) { + warn_at('unexpected_a', line, character, + '(space)'); + } + character += 1; + source_row = ''; + } else { + stop_at('unexpected_a', line, character, + source_row.charAt(0)); + } + } + } else { + +// identifier + + c = snippet.charAt(0); + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', snippet); + } + +// number + + if (c.isDigit()) { + return number(snippet); + } + switch (snippet) { + +// string + + case '"': + case "'": + return string(snippet); + +// // comment + + case '//': + comment(source_row); + source_row = ''; + break; + +// /* comment + + case '/*': + for (;;) { + i = source_row.search(lx); + if (i >= 0) { + break; + } + comment(source_row); + if (!next_line()) { + stop_at('unclosed_comment', line, character); + } + } + comment(source_row.slice(0, i)); + character += i + 2; + if (source_row.charAt(i) === '/') { + stop_at('nested_comment', line, character); + } + source_row = source_row.slice(i + 2); + break; + + case '': + break; +// / + case '/': + if (token.id === '/=') { + stop_at( + bundle.slash_equal, + line, + from + ); + } + return prereg + ? regexp() + : it('(punctuator)', snippet); + +// punctuator + + case ''); + } + character += 3; + source_row = source_row.slice(i + 3); + break; + case '#': + if (xmode === 'html' || xmode === 'styleproperty') { + for (;;) { + c = source_row.charAt(0); + if ((c < '0' || c > '9') && + (c < 'a' || c > 'f') && + (c < 'A' || c > 'F')) { + break; + } + character += 1; + source_row = source_row.slice(1); + snippet += c; + } + if (snippet.length !== 4 && snippet.length !== 7) { + warn_at('bad_color_a', line, + from + length, snippet); + } + return it('(color)', snippet); + } + return it('(punctuator)', snippet); + + default: + if (xmode === 'outer' && c === '&') { + character += 1; + source_row = source_row.slice(1); + for (;;) { + c = source_row.charAt(0); + character += 1; + source_row = source_row.slice(1); + if (c === ';') { + break; + } + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + c === '#')) { + stop_at('bad_entity', line, from + length, + character); + } + } + break; + } + return it('(punctuator)', snippet); + } + } + } + } + }; + }()); + + + function add_label(token, kind, name) { + +// Define the symbol in the current function in the current scope. + + name = name || token.string; + +// Global variables cannot be created in the safe subset. If a global variable +// already exists, do nothing. If it is predefined, define it. + + if (funct === global_funct) { + if (option.safe) { + warn('adsafe_a', token, name); + } + if (typeof global_funct[name] !== 'string') { + token.writeable = typeof predefined[name] === 'boolean' + ? predefined[name] + : true; + global_scope[name] = token; + } + if (kind === 'becoming') { + kind = 'var'; + } + +// Ordinary variables. + + } else { + +// Warn if the variable already exists. + + if (typeof funct[name] === 'string') { + if (funct[name] === 'undef') { + if (!option.undef) { + warn('used_before_a', token, name); + } + kind = 'var'; + } else { + warn('already_defined', token, name); + } + } else { + +// Add the symbol to the current function. + + token.writeable = true; + scope[name] = token; + } + } + token.function = funct; + funct[name] = kind; + } + + + function peek(distance) { + +// Peek ahead to a future token. The distance is how far ahead to look. The +// default is the next token. + + var found, slot = 0; + + distance = distance || 0; + while (slot <= distance) { + found = lookahead[slot]; + if (!found) { + found = lookahead[slot] = lex.token(); + } + slot += 1; + } + return found; + } + + + function advance(id, match) { + +// Produce the next token, also looking for programming errors. + + if (indent) { + +// If indentation checking was requested, then inspect all of the line breakings. +// The var statement is tricky because the names might be aligned or not. We +// look at the first line break after the var to determine the programmer's +// intention. + + if (var_mode && next_token.line !== token.line) { + if ((var_mode !== indent || !next_token.edge) && + next_token.from === indent.at - + (next_token.edge ? option.indent : 0)) { + var dent = indent; + for (;;) { + dent.at -= option.indent; + if (dent === var_mode) { + break; + } + dent = dent.was; + } + dent.open = false; + } + var_mode = null; + } + if (next_token.id === '?' && indent.mode === ':' && + token.line !== next_token.line) { + indent.at -= option.indent; + } + if (indent.open) { + +// If the token is an edge. + + if (next_token.edge) { + if (next_token.edge === 'label') { + expected_at(1); + } else if (next_token.edge === 'case' || indent.mode === 'statement') { + expected_at(indent.at - option.indent); + } else if (indent.mode !== 'array' || next_token.line !== token.line) { + expected_at(indent.at); + } + +// If the token is not an edge, but is the first token on the line. + + } else if (next_token.line !== token.line) { + if (next_token.from < indent.at + (indent.mode === + 'expression' ? 0 : option.indent)) { + expected_at(indent.at + option.indent); + } + indent.wrap = true; + } + } else if (next_token.line !== token.line) { + if (next_token.edge) { + expected_at(indent.at); + } else { + indent.wrap = true; + if (indent.mode === 'statement' || indent.mode === 'var') { + expected_at(indent.at + option.indent); + } else if (next_token.from < indent.at + (indent.mode === + 'expression' ? 0 : option.indent)) { + expected_at(indent.at + option.indent); + } + } + } + } + + switch (token.id) { + case '(number)': + if (next_token.id === '.') { + warn('trailing_decimal_a'); + } + break; + case '-': + if (next_token.id === '-' || next_token.id === '--') { + warn('confusing_a'); + } + break; + case '+': + if (next_token.id === '+' || next_token.id === '++') { + warn('confusing_a'); + } + break; + } + if (token.id === '(string)' || token.identifier) { + anonname = token.string; + } + + if (id && next_token.id !== id) { + if (match) { + warn('expected_a_b_from_c_d', next_token, id, + match.id, match.line, artifact()); + } else if (!next_token.identifier || next_token.string !== id) { + warn('expected_a_b', next_token, id, artifact()); + } + } + prev_token = token; + token = next_token; + next_token = lookahead.shift() || lex.token(); + next_token.function = funct; + tokens.push(next_token); + } + + + function advance_identifier(string) { + if (next_token.identifier && next_token.string === string) { + advance(); + } else { + warn('expected_a_b', next_token, string, artifact()); + } + } + + + function do_safe() { + if (option.adsafe) { + option.safe = true; + } + if (option.safe) { + option.browser = + option.continue = + option.css = + option.debug = + option.devel = + option.evil = + option.forin = + option.newcap = + option.nomen = + option.on = + option.rhino = + option.sloppy = + option.sub = + option.undef = + option.windows = false; + + + delete predefined.Array; + delete predefined.Date; + delete predefined.Function; + delete predefined.Object; + delete predefined.eval; + + add_to_predefined({ + ADSAFE: false, + lib: false + }); + } + } + + + function do_globals() { + var name, writeable; + for (;;) { + if (next_token.id !== '(string)' && !next_token.identifier) { + return; + } + name = next_token.string; + advance(); + writeable = false; + if (next_token.id === ':') { + advance(':'); + switch (next_token.id) { + case 'true': + writeable = predefined[name] !== false; + advance('true'); + break; + case 'false': + advance('false'); + break; + default: + stop('unexpected_a'); + } + } + predefined[name] = writeable; + if (next_token.id !== ',') { + return; + } + advance(','); + } + } + + + function do_jslint() { + var name, value; + while (next_token.id === '(string)' || next_token.identifier) { + name = next_token.string; + if (!allowed_option[name]) { + stop('unexpected_a'); + } + advance(); + if (next_token.id !== ':') { + stop('expected_a_b', next_token, ':', artifact()); + } + advance(':'); + if (typeof allowed_option[name] === 'number') { + value = next_token.number; + if (value > allowed_option[name] || value <= 0 || + Math.floor(value) !== value) { + stop('expected_small_a'); + } + option[name] = value; + } else { + if (next_token.id === 'true') { + option[name] = true; + } else if (next_token.id === 'false') { + option[name] = false; + } else { + stop('unexpected_a'); + } + } + advance(); + if (next_token.id === ',') { + advance(','); + } + } + assume(); + } + + + function do_properties() { + var name; + option.properties = true; + for (;;) { + if (next_token.id !== '(string)' && !next_token.identifier) { + return; + } + name = next_token.string; + advance(); + if (next_token.id === ':') { + for (;;) { + advance(); + if (next_token.id !== '(string)' && !next_token.identifier) { + break; + } + } + } + property[name] = 0; + if (next_token.id !== ',') { + return; + } + advance(','); + } + } + + + directive = function directive() { + var command = this.id, + old_comments_off = comments_off, + old_indent = indent; + comments_off = true; + indent = null; + if (next_token.line === token.line && next_token.from === token.thru) { + warn('missing_space_a_b', next_token, artifact(token), artifact()); + } + if (lookahead.length > 0) { + warn('unexpected_a', this); + } + switch (command) { + case '/*properties': + case '/*property': + case '/*members': + case '/*member': + do_properties(); + break; + case '/*jslint': + if (option.safe) { + warn('adsafe_a', this); + } + do_jslint(); + break; + case '/*globals': + case '/*global': + if (option.safe) { + warn('adsafe_a', this); + } + do_globals(); + break; + default: + stop('unexpected_a', this); + } + comments_off = old_comments_off; + advance('*/'); + indent = old_indent; + }; + + +// Indentation intention + + function edge(mode) { + next_token.edge = indent ? indent.open && (mode || 'edge') : ''; + } + + + function step_in(mode) { + var open; + if (typeof mode === 'number') { + indent = { + at: +mode, + open: true, + was: indent + }; + } else if (!indent) { + indent = { + at: 1, + mode: 'statement', + open: true + }; + } else if (mode === 'statement') { + indent = { + at: indent.at, + open: true, + was: indent + }; + } else { + open = mode === 'var' || next_token.line !== token.line; + indent = { + at: (open || mode === 'control' + ? indent.at + option.indent + : indent.at) + (indent.wrap ? option.indent : 0), + mode: mode, + open: open, + was: indent + }; + if (mode === 'var' && open) { + var_mode = indent; + } + } + } + + function step_out(id, symbol) { + if (id) { + if (indent && indent.open) { + indent.at -= option.indent; + edge(); + } + advance(id, symbol); + } + if (indent) { + indent = indent.was; + } + } + +// Functions for conformance of whitespace. + + function one_space(left, right) { + left = left || token; + right = right || next_token; + if (right.id !== '(end)' && !option.white && + (token.line !== right.line || + token.thru + 1 !== right.from)) { + warn('expected_space_a_b', right, artifact(token), artifact(right)); + } + } + + function one_space_only(left, right) { + left = left || token; + right = right || next_token; + if (right.id !== '(end)' && (left.line !== right.line || + (!option.white && left.thru + 1 !== right.from))) { + warn('expected_space_a_b', right, artifact(left), artifact(right)); + } + } + + function no_space(left, right) { + left = left || token; + right = right || next_token; + if ((!option.white || xmode === 'styleproperty' || xmode === 'style') && + left.thru !== right.from && left.line === right.line) { + warn('unexpected_space_a_b', right, artifact(left), artifact(right)); + } + } + + function no_space_only(left, right) { + left = left || token; + right = right || next_token; + if (right.id !== '(end)' && (left.line !== right.line || + (!option.white && left.thru !== right.from))) { + warn('unexpected_space_a_b', right, artifact(left), artifact(right)); + } + } + + function spaces(left, right) { + if (!option.white) { + left = left || token; + right = right || next_token; + if (left.thru === right.from && left.line === right.line) { + warn('missing_space_a_b', right, artifact(left), artifact(right)); + } + } + } + + function comma() { + if (next_token.id !== ',') { + warn_at('expected_a_b', token.line, token.thru, ',', artifact()); + } else { + if (!option.white) { + no_space_only(); + } + advance(','); + spaces(); + } + } + + + function semicolon() { + if (next_token.id !== ';') { + warn_at('expected_a_b', token.line, token.thru, ';', artifact()); + } else { + if (!option.white) { + no_space_only(); + } + advance(';'); + if (semicolon_coda[next_token.id] !== true) { + spaces(); + } + } + } + + function use_strict() { + if (next_token.string === 'use strict') { + if (strict_mode) { + warn('unnecessary_use'); + } + edge(); + advance(); + semicolon(); + strict_mode = true; + option.undef = false; + return true; + } + return false; + } + + + function are_similar(a, b) { + if (a === b) { + return true; + } + if (Array.isArray(a)) { + if (Array.isArray(b) && a.length === b.length) { + var i; + for (i = 0; i < a.length; i += 1) { + if (!are_similar(a[i], b[i])) { + return false; + } + } + return true; + } + return false; + } + if (Array.isArray(b)) { + return false; + } + if (a.id === '(number)' && b.id === '(number)') { + return a.number === b.number; + } + if (a.arity === b.arity && a.string === b.string) { + switch (a.arity) { + case 'prefix': + case 'suffix': + case undefined: + return a.id === b.id && are_similar(a.first, b.first) && + a.id !== '{' && a.id !== '['; + case 'infix': + return are_similar(a.first, b.first) && + are_similar(a.second, b.second); + case 'ternary': + return are_similar(a.first, b.first) && + are_similar(a.second, b.second) && + are_similar(a.third, b.third); + case 'function': + case 'regexp': + return false; + default: + return true; + } + } else { + if (a.id === '.' && b.id === '[' && b.arity === 'infix') { + return a.second.string === b.second.string && b.second.id === '(string)'; + } + if (a.id === '[' && a.arity === 'infix' && b.id === '.') { + return a.second.string === b.second.string && a.second.id === '(string)'; + } + } + return false; + } + + +// This is the heart of JSLINT, 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) { + +// rbp is the right binding power. +// initial indicates that this is the first expression of a statement. + + var left; + if (next_token.id === '(end)') { + stop('unexpected_a', token, next_token.id); + } + advance(); + if (option.safe && scope[token.string] && + scope[token.string] === global_scope[token.string] && + (next_token.id !== '(' && next_token.id !== '.')) { + warn('adsafe_a', token); + } + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.string; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (next_token.id === '(number)' && token.id === '.') { + warn('leading_decimal_a', token, artifact()); + advance(); + return token; + } + stop('expected_identifier_a', token, token.id); + } + while (rbp < next_token.lbp) { + advance(); + if (token.led) { + left = token.led(left); + } else { + stop('expected_operator_a', token, token.id); + } + } + } + return left; + } + + +// 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 || 0, + string: s + }; + } + return x; + } + + function postscript(x) { + x.postscript = true; + return x; + } + + function ultimate(s) { + var x = symbol(s, 0); + x.from = 1; + x.thru = 1; + x.line = 0; + x.edge = 'edge'; + x.string = s; + return postscript(x); + } + + + function stmt(s, f) { + var x = symbol(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + function labeled_stmt(s, f) { + var x = stmt(s, f); + x.labeled = true; + } + + function disrupt_stmt(s, f) { + var x = stmt(s, f); + x.disrupt = true; + } + + + function reserve_name(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); + reserve_name(x); + x.nud = function () { + var that = this; + that.arity = 'prefix'; + if (typeof f === 'function') { + that = f(that); + if (that.arity !== 'prefix') { + return that; + } + } else { + if (s === 'typeof') { + one_space(); + } else { + no_space_only(); + } + that.first = expression(150); + } + switch (that.id) { + case '++': + case '--': + if (!option.plusplus) { + warn('unexpected_a', that); + } else if ((!that.first.identifier || that.first.reserved) && + that.first.id !== '.' && that.first.id !== '[') { + warn('bad_operand', that); + } + break; + default: + if (that.first.arity === 'prefix' || + that.first.arity === 'function') { + warn('unexpected_a', that); + } + } + return that; + }; + return x; + } + + + function type(s, t, nud) { + var x = symbol(s); + x.arity = t; + if (nud) { + x.nud = nud; + } + return x; + } + + + function reserve(s, f) { + var x = symbol(s); + x.identifier = x.reserved = true; + if (typeof f === 'function') { + x.nud = f; + } + return x; + } + + + function constant(name) { + var x = reserve(name); + x.string = name; + x.nud = return_this; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, p, f, w) { + var x = symbol(s, p); + reserve_name(x); + x.led = function (left) { + this.arity = 'infix'; + if (!w) { + spaces(prev_token, token); + spaces(); + } + if (!option.bitwise && this.bitwise) { + warn('unexpected_a', this); + } + if (typeof f === 'function') { + return f(left, this); + } + this.first = left; + this.second = expression(p); + return this; + }; + return x; + } + + function expected_relation(node, message) { + if (node.assign) { + warn(message || bundle.conditional_assignment, node); + } + return node; + } + + function expected_condition(node, message) { + switch (node.id) { + case '[': + case '-': + if (node.arity !== 'infix') { + warn(message || bundle.weird_condition, node); + } + break; + case 'false': + case 'function': + case 'Infinity': + case 'NaN': + case 'null': + case 'true': + case 'undefined': + case 'void': + case '(number)': + case '(regexp)': + case '(string)': + case '{': + case '?': + case '~': + warn(message || bundle.weird_condition, node); + break; + case '(': + if (node.first.id === 'new' || + (node.first.string === 'Boolean') || + (node.first.id === '.' && + numbery[node.first.second.string] === true)) { + warn(message || bundle.weird_condition, node); + } + break; + } + return node; + } + + function check_relation(node) { + switch (node.arity) { + case 'prefix': + switch (node.id) { + case '{': + case '[': + warn('unexpected_a', node); + break; + case '!': + warn('confusing_a', node); + break; + } + break; + case 'function': + case 'regexp': + warn('unexpected_a', node); + break; + default: + if (node.id === 'NaN') { + warn('isNaN', node); + } + } + return node; + } + + + function relation(s, eqeq) { + return infix(s, 100, function (left, that) { + check_relation(left); + if (eqeq && !option.eqeq) { + warn('expected_a_b', that, eqeq, that.id); + } + var right = expression(100); + if (are_similar(left, right) || + ((left.id === '(string)' || left.id === '(number)') && + (right.id === '(string)' || right.id === '(number)'))) { + warn('weird_relation', that); + } else if (left.id === 'typeof') { + if (right.id !== '(string)') { + warn("expected_string_a", right, right.id === '(number)' + ? right.number + : right.string); + } else if (right.string === 'undefined' || + right.string === 'null') { + warn("unexpected_typeof_a", left, right.string); + } + } else if (right.id === 'typeof') { + if (left.id !== '(string)') { + warn("expected_string_a", left, left.id === '(number)' + ? left.number + : left.string); + } else if (left.string === 'undefined' || + left.string === 'null') { + warn("unexpected_typeof_a", right, left.string); + } + } + that.first = left; + that.second = check_relation(right); + return that; + }); + } + + + function assignop(s, op) { + var x = infix(s, 20, function (left, that) { + var l; + that.first = left; + if (left.identifier) { + if (scope[left.string]) { + if (scope[left.string].writeable === false) { + warn('read_only', left); + } + } else { + stop('read_only'); + } + if (funct['(params)']) { + funct['(params)'].forEach(function (value) { + if (value.string === left.string) { + value.assign = true; + } + }); + } + } else if (option.safe) { + l = left; + do { + if (typeof predefined[l.string] === 'boolean') { + warn('adsafe_a', l); + } + l = l.first; + } while (l); + } + if (left === syntax.function) { + warn('identifier_function', token); + } + if (left.id === '.' || left.id === '[') { + if (!left.first || left.first.string === 'arguments') { + warn('bad_assignment', that); + } + } else if (left.identifier) { + if (!left.reserved && funct[left.string] === 'exception') { + warn('assign_exception', left); + } + } else { + warn('bad_assignment', that); + } + that.second = expression(19); + if (that.id === '=' && are_similar(that.first, that.second)) { + warn('weird_assignment', that); + } + return that; + }); + x.assign = true; + if (op) { + if (syntax[op].bitwise) { + x.bitwise = true; + } + } + return x; + } + + + function bitwise(s, p) { + var x = infix(s, p, 'number'); + x.bitwise = true; + return x; + } + + + function suffix(s) { + var x = symbol(s, 150); + x.led = function (left) { + no_space_only(prev_token, token); + if (!option.plusplus) { + warn('unexpected_a', this); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warn('bad_operand', this); + } + this.first = left; + this.arity = 'suffix'; + return this; + }; + return x; + } + + + function optional_identifier(variable) { + if (next_token.identifier) { + advance(); + if (option.safe && banned[token.string]) { + warn('adsafe_a', token); + } else if (token.reserved && (!option.es5 || variable)) { + warn('expected_identifier_a_reserved', token); + } + return token.string; + } + } + + + function identifier(variable) { + var i = optional_identifier(variable); + if (!i) { + stop(token.id === 'function' && next_token.id === '(' + ? 'name_function' + : 'expected_identifier_a'); + } + return i; + } + + + function statement() { + + var label, old_scope = scope, the_statement; + +// We don't like the empty statement. + + if (next_token.id === ';') { + warn('unexpected_a'); + semicolon(); + return; + } + +// Is this a labeled statement? + + if (next_token.identifier && !next_token.reserved && peek().id === ':') { + edge('label'); + label = next_token; + advance(); + advance(':'); + scope = Object.create(old_scope); + add_label(label, 'label'); + if (next_token.labeled !== true || funct === global_funct) { + stop('unexpected_label_a', label); + } else if (jx.test(label.string + ':')) { + warn('url', label); + } + next_token.label = label; + } + +// Parse the statement. + + if (token.id !== 'else') { + edge(); + } + step_in('statement'); + the_statement = expression(0, true); + if (the_statement) { + +// Look for the final semicolon. + + if (the_statement.arity === 'statement') { + if (the_statement.id === 'switch' || + (the_statement.block && the_statement.id !== 'do')) { + spaces(); + } else { + semicolon(); + } + } else { + +// If this is an expression statement, determine if it is acceptable. +// We do not like +// new Blah; +// statements. If it is to be used at all, new should only be used to make +// objects, not side effects. The expression statements we do like do +// assignment or invocation or delete. + + if (the_statement.id === '(') { + if (the_statement.first.id === 'new') { + warn('bad_new'); + } + } else if (!the_statement.assign && + the_statement.id !== 'delete' && + the_statement.id !== '++' && + the_statement.id !== '--') { + warn('assignment_function_expression', the_statement); + } + semicolon(); + } + } + step_out(); + scope = old_scope; + return the_statement; + } + + + function statements() { + var array = [], disruptor, the_statement; + +// A disrupt statement may not be followed by any other statement. +// If the last statement is disrupt, then the sequence is disrupt. + + while (next_token.postscript !== true) { + if (next_token.id === ';') { + warn('unexpected_a', next_token); + semicolon(); + } else { + if (next_token.string === 'use strict') { + if ((!node_js && xmode !== 'script') || funct !== global_funct || array.length > 0) { + warn('function_strict'); + } + use_strict(); + } + if (disruptor) { + warn('unreachable_a_b', next_token, next_token.string, + disruptor.string); + disruptor = null; + } + the_statement = statement(); + if (the_statement) { + array.push(the_statement); + if (the_statement.disrupt) { + disruptor = the_statement; + array.disrupt = true; + } + } + } + } + return array; + } + + + function block(ordinary) { + +// array block is array sequence of statements wrapped in braces. +// ordinary is false for function bodies and try blocks. +// ordinary is true for if statements, while, etc. + + var array, + curly = next_token, + old_in_block = in_block, + old_scope = scope, + old_strict_mode = strict_mode; + + in_block = ordinary; + scope = Object.create(scope); + spaces(); + if (next_token.id === '{') { + advance('{'); + step_in(); + if (!ordinary && !use_strict() && !old_strict_mode && + !option.sloppy && funct['(context)'] === global_funct) { + warn('missing_use_strict'); + } + array = statements(); + strict_mode = old_strict_mode; + step_out('}', curly); + } else if (!ordinary) { + stop('expected_a_b', next_token, '{', artifact()); + } else { + warn('expected_a_b', next_token, '{', artifact()); + array = [statement()]; + array.disrupt = array[0].disrupt; + } + funct['(verb)'] = null; + scope = old_scope; + in_block = old_in_block; + if (ordinary && array.length === 0) { + warn('empty_block'); + } + return array; + } + + + function tally_property(name) { + if (option.properties && typeof property[name] !== 'number') { + warn('unexpected_property_a', token, name); + } + if (typeof property[name] === 'number') { + property[name] += 1; + } else { + property[name] = 1; + } + } + + +// ECMAScript parser + + syntax['(identifier)'] = { + id: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var name = this.string, + variable = scope[name], + site, + writeable; + +// If the variable is not in scope, then we may have an undeclared variable. +// Check the predefined list. If it was predefined, create the global +// variable. + + if (typeof variable !== 'object') { + writeable = predefined[name]; + if (typeof writeable === 'boolean') { + global_scope[name] = variable = { + string: name, + writeable: writeable, + function: global_funct + }; + global_funct[name] = 'var'; + +// But if the variable is not in scope, and is not predefined, and if we are not +// in the global scope, then we have an undefined variable error. + + } else { + if (!option.undef) { + warn('used_before_a', token); + } + scope[name] = variable = { + string: name, + writeable: true, + function: funct + }; + funct[name] = 'undef'; + } + + } + site = variable.function; + +// The name is in scope and defined in the current function. + + if (funct === site) { + +// Change 'unused' to 'var', and reject labels. + + switch (funct[name]) { + case 'becoming': + warn('unexpected_a', token); + funct[name] = 'var'; + break; + case 'unused': + funct[name] = 'var'; + break; + case 'unparam': + funct[name] = 'parameter'; + break; + case 'unction': + funct[name] = 'function'; + break; + case 'label': + warn('a_label', token, name); + break; + } + this.function = funct; + +// If the name is already defined in the current +// function, but not as outer, then there is a scope error. + + } else { + switch (funct[name]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warn('a_scope', token, name); + break; + case 'label': + warn('a_label', token, name); + 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. + + switch (site[name]) { + case 'becoming': + case 'closure': + case 'function': + case 'parameter': + case 'unction': + case 'unused': + case 'var': + site[name] = 'closure'; + funct[name] = site === global_funct + ? 'global' + : 'outer'; + this.function = site; + break; + case 'unparam': + site[name] = 'parameter'; + funct[name] = 'outer'; + this.function = site; + break; + case 'undef': + funct[name] = 'undef'; + break; + case 'label': + warn('a_label', token, name); + break; + } + } + } + return this; + }, + led: function () { + stop('expected_operator_a'); + } + }; + +// Build the syntax table by declaring the syntactic elements. + + type('(array)', 'array'); + type('(color)', 'color'); + type('(function)', 'function'); + type('(number)', 'number', return_this); + type('(object)', 'object'); + type('(string)', 'string', return_this); + type('(boolean)', 'boolean', return_this); + type('(range)', 'range'); + type('(regexp)', 'regexp', return_this); + + ultimate('(begin)'); + ultimate('(end)'); + ultimate('(error)'); + postscript(symbol(''); + postscript(symbol('}')); + symbol(')'); + symbol(']'); + postscript(symbol('"')); + postscript(symbol('\'')); + symbol(';'); + symbol(':'); + symbol(','); + symbol('#'); + symbol('@'); + symbol('*/'); + postscript(reserve('case')); + reserve('catch'); + postscript(reserve('default')); + reserve('else'); + reserve('finally'); + + reservevar('arguments', function (x) { + if (strict_mode && funct === global_funct) { + warn('strict', x); + } else if (option.safe) { + warn('adsafe_a', x); + } + funct['(arguments)'] = true; + }); + reservevar('eval', function (x) { + if (option.safe) { + warn('adsafe_a', x); + } + }); + constant('false', 'boolean'); + constant('Infinity', 'number'); + constant('NaN', 'number'); + constant('null', ''); + reservevar('this', function (x) { + if (option.safe) { + warn('adsafe_a', x); + } else if (strict_mode && funct['(token)'] && + (funct['(token)'].arity === 'statement' && + funct['(name)'].charAt(0) > 'Z')) { + warn('strict', x); + } + }); + constant('true', 'boolean'); + constant('undefined', ''); + + infix('?', 30, function (left, that) { + step_in('?'); + that.first = expected_condition(expected_relation(left)); + that.second = expression(0); + spaces(); + step_out(); + var colon = next_token; + advance(':'); + step_in(':'); + spaces(); + that.third = expression(10); + that.arity = 'ternary'; + if (are_similar(that.second, that.third)) { + warn('weird_ternary', colon); + } else if (are_similar(that.first, that.second)) { + warn('use_or', that); + } + step_out(); + return that; + }); + + infix('||', 40, function (left, that) { + function paren_check(that) { + if (that.id === '&&' && !that.paren) { + warn('and', that); + } + return that; + } + + that.first = paren_check(expected_condition(expected_relation(left))); + that.second = paren_check(expected_relation(expression(40))); + if (are_similar(that.first, that.second)) { + warn('weird_condition', that); + } + return that; + }); + + infix('&&', 50, function (left, that) { + that.first = expected_condition(expected_relation(left)); + that.second = expected_relation(expression(50)); + if (are_similar(that.first, that.second)) { + warn('weird_condition', that); + } + return that; + }); + + prefix('void', function (that) { + that.first = expression(0); + if (option.es5 || strict_mode) { + warn('expected_a_b', that, 'undefined', 'void'); + } else if (that.first.number !== 0) { + warn('expected_a_b', that.first, '0', artifact(that.first)); + } + return that; + }); + + bitwise('|', 70); + bitwise('^', 80); + bitwise('&', 90); + + relation('==', '==='); + relation('==='); + relation('!=', '!=='); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + + bitwise('<<', 120); + bitwise('>>', 120); + bitwise('>>>', 120); + + infix('in', 120, function (left, that) { + warn('infix_in', that); + that.left = left; + that.right = expression(130); + return that; + }); + infix('instanceof', 120); + infix('+', 130, function (left, that) { + if (left.id === '(number)') { + if (left.number === 0) { + warn('unexpected_a', left, '0'); + } + } else if (left.id === '(string)') { + if (left.string === '') { + warn('expected_a_b', left, 'String', '\'\''); + } + } + var right = expression(130); + if (right.id === '(number)') { + if (right.number === 0) { + warn('unexpected_a', right, '0'); + } + } else if (right.id === '(string)') { + if (right.string === '') { + warn('expected_a_b', right, 'String', '\'\''); + } + } + if (left.id === right.id) { + if (left.id === '(string)' || left.id === '(number)') { + if (left.id === '(string)') { + left.string += right.string; + if (jx.test(left.string)) { + warn('url', left); + } + } else { + left.number += right.number; + } + left.thru = right.thru; + return left; + } + } + that.first = left; + that.second = right; + return that; + }); + prefix('+'); + prefix('+++', function () { + warn('confusing_a', token); + this.first = expression(150); + this.arity = 'prefix'; + return this; + }); + infix('+++', 130, function (left) { + warn('confusing_a', token); + this.first = left; + this.second = expression(130); + return this; + }); + infix('-', 130, function (left, that) { + if ((left.id === '(number)' && left.number === 0) || left.id === '(string)') { + warn('unexpected_a', left); + } + var right = expression(130); + if ((right.id === '(number)' && right.number === 0) || right.id === '(string)') { + warn('unexpected_a', right); + } + if (left.id === right.id && left.id === '(number)') { + left.number -= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + prefix('-'); + prefix('---', function () { + warn('confusing_a', token); + this.first = expression(150); + this.arity = 'prefix'; + return this; + }); + infix('---', 130, function (left) { + warn('confusing_a', token); + this.first = left; + this.second = expression(130); + return this; + }); + infix('*', 140, function (left, that) { + if ((left.id === '(number)' && (left.number === 0 || left.number === 1)) || left.id === '(string)') { + warn('unexpected_a', left); + } + var right = expression(140); + if ((right.id === '(number)' && (right.number === 0 || right.number === 1)) || right.id === '(string)') { + warn('unexpected_a', right); + } + if (left.id === right.id && left.id === '(number)') { + left.number *= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + infix('/', 140, function (left, that) { + if ((left.id === '(number)' && left.number === 0) || left.id === '(string)') { + warn('unexpected_a', left); + } + var right = expression(140); + if ((right.id === '(number)' && (right.number === 0 || right.number === 1)) || right.id === '(string)') { + warn('unexpected_a', right); + } + if (left.id === right.id && left.id === '(number)') { + left.number /= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + infix('%', 140, function (left, that) { + if ((left.id === '(number)' && (left.number === 0 || left.number === 1)) || left.id === '(string)') { + warn('unexpected_a', left); + } + var right = expression(140); + if ((right.id === '(number)' && right.number === 0) || right.id === '(string)') { + warn('unexpected_a', right); + } + if (left.id === right.id && left.id === '(number)') { + left.number %= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + + suffix('++'); + prefix('++'); + + suffix('--'); + prefix('--'); + prefix('delete', function (that) { + one_space(); + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warn('deleted'); + } + that.first = p; + return that; + }); + + + prefix('~', function (that) { + no_space_only(); + if (!option.bitwise) { + warn('unexpected_a', that); + } + that.first = expression(150); + return that; + }); + function banger(that) { + no_space_only(); + that.first = expected_condition(expression(150)); + if (bang[that.first.id] === that || that.first.assign) { + warn('confusing_a', that); + } + return that; + } + prefix('!', banger); + prefix('!!', banger); + prefix('typeof'); + prefix('new', function (that) { + one_space(); + var c = expression(160), n, p, v; + that.first = c; + if (c.id !== 'function') { + if (c.identifier) { + switch (c.string) { + case 'Object': + warn('use_object', token); + break; + case 'Array': + if (next_token.id === '(') { + p = next_token; + p.first = this; + advance('('); + if (next_token.id !== ')') { + n = expression(0); + p.second = [n]; + if (n.id !== '(number)' || next_token.id === ',') { + warn('use_array', p); + } + while (next_token.id === ',') { + advance(','); + p.second.push(expression(0)); + } + } else { + warn('use_array', token); + } + advance(')', p); + return p; + } + warn('use_array', token); + break; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warn('not_a_constructor', c); + break; + case 'Function': + if (!option.evil) { + warn('function_eval'); + } + break; + case 'Date': + case 'RegExp': + case 'this': + break; + default: + if (c.id !== 'function') { + v = c.string.charAt(0); + if (!option.newcap && (v < 'A' || v > 'Z')) { + warn('constructor_name_a', token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warn('bad_constructor', token); + } + } + } else { + warn('weird_new', that); + } + if (next_token.id !== '(') { + warn('missing_a', next_token, '()'); + } + return that; + }); + + infix('(', 160, function (left, that) { + var e, p; + if (indent && indent.mode === 'expression') { + no_space(prev_token, token); + } else { + no_space_only(prev_token, token); + } + if (!left.immed && left.id === 'function') { + warn('wrap_immediate'); + } + p = []; + if (left.identifier) { + if (left.string.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.string !== 'Number' && left.string !== 'String' && + left.string !== 'Boolean' && left.string !== 'Date') { + if (left.string === 'Math' || left.string === 'JSON') { + warn('not_a_function', left); + } else if (left.string === 'Object') { + warn('use_object', token); + } else if (left.string === 'Array' || !option.newcap) { + warn('missing_a', left, 'new'); + } + } + } + } else if (left.id === '.') { + if (option.safe && left.first.string === 'Math' && + left.second === 'random') { + warn('adsafe_a', left); + } else if (left.second.string === 'split' && + left.first.id === '(string)') { + warn('use_array', left.second); + } + } + step_in(); + if (next_token.id !== ')') { + no_space(); + for (;;) { + edge(); + e = expression(10); + if (left.string === 'Boolean' && (e.id === '!' || e.id === '~')) { + warn('weird_condition', e); + } + p.push(e); + if (next_token.id !== ',') { + break; + } + comma(); + } + } + no_space(); + step_out(')', that); + if (typeof left === 'object') { + if (left.string === 'parseInt' && p.length === 1) { + warn('radix', left); + } else if (left.string === 'String' && p.length >= 1 && p[0].id === '(string)') { + warn('unexpected_a', left); + } + if (!option.evil) { + if (left.string === 'eval' || left.string === 'Function' || + left.string === 'execScript') { + warn('evil', left); + } else if (p[0] && p[0].id === '(string)' && + (left.string === 'setTimeout' || + left.string === 'setInterval')) { + warn('implied_evil', left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warn('bad_invocation', left); + } + if (left.id === '.') { + if (p.length > 0 && + left.first && left.first.first && + are_similar(p[0], left.first.first)) { + if (left.second.string === 'call' || + (left.second.string === 'apply' && (p.length === 1 || + (p[1].arity === 'prefix' && p[1].id === '[')))) { + warn('unexpected_a', left.second); + } + } + if (left.second.string === 'toString') { + if (left.first.id === '(string)' || left.first.id === '(number)') { + warn('unexpected_a', left.second); + } + } + } + } + that.first = left; + that.second = p; + return that; + }, true); + + prefix('(', function (that) { + step_in('expression'); + no_space(); + edge(); + if (next_token.id === 'function') { + next_token.immed = true; + } + var value = expression(0); + value.paren = true; + no_space(); + step_out(')', that); + if (value.id === 'function') { + switch (next_token.id) { + case '(': + warn('move_invocation'); + break; + case '.': + case '[': + warn('unexpected_a'); + break; + default: + warn('bad_wrap', that); + } + } else if (!value.arity) { + warn('unexpected_a', that); + } + return value; + }); + + infix('.', 170, function (left, that) { + no_space(prev_token, token); + no_space(); + var name = identifier(); + if (typeof name === 'string') { + tally_property(name); + } + that.first = left; + that.second = token; + if (left && left.string === 'arguments' && + (name === 'callee' || name === 'caller')) { + warn('avoid_a', left, 'arguments.' + name); + } else if (!option.evil && left && left.string === 'document' && + (name === 'write' || name === 'writeln')) { + warn('write_is_wrong', left); + } else if (!option.stupid && syx.test(name)) { + warn('sync_a', token); + } else if (option.adsafe) { + if (!adsafe_top && left.string === 'ADSAFE') { + if (name === 'id' || name === 'lib') { + warn('adsafe_a', that); + } else if (name === 'go') { + if (xmode !== 'script') { + warn('adsafe_a', that); + } else if (adsafe_went || next_token.id !== '(' || + peek(0).id !== '(string)' || + peek(0).string !== adsafe_id || + peek(1).id !== ',') { + stop('adsafe_a', that, 'go'); + } + adsafe_went = true; + adsafe_may = false; + } + } + adsafe_top = false; + } + if (!option.evil && (name === 'eval' || name === 'execScript')) { + warn('evil'); + } else if (option.safe) { + for (;;) { + if (banned[name] === true) { + warn('adsafe_a', token, name); + } + if (typeof predefined[left.string] !== 'boolean' || //// check for writeable + next_token.id === '(') { + break; + } + if (next_token.id !== '.') { + warn('adsafe_a', that); + break; + } + advance('.'); + token.first = that; + token.second = name; + that = token; + name = identifier(); + if (typeof name === 'string') { + tally_property(name); + } + } + } + return that; + }, true); + + infix('[', 170, function (left, that) { + var e, s; + no_space_only(prev_token, token); + no_space(); + step_in(); + edge(); + e = expression(0); + switch (e.id) { + case '(number)': + if (e.id === '(number)' && left.id === 'arguments') { + warn('use_param', left); + } + break; + case '(string)': + if (option.safe && (banned[e.string] || + e.string.charAt(0) === '_' || e.string.slice(-1) === '_')) { + warn('adsafe_subscript_a', e); + } else if (!option.evil && + (e.string === 'eval' || e.string === 'execScript')) { + warn('evil', e); + } else if (!option.sub && ix.test(e.string)) { + s = syntax[e.string]; + if (!s || !s.reserved) { + warn('subscript', e); + } + } + tally_property(e.string); + break; + default: + if (option.safe) { + if ((e.id !== '+' || e.arity !== 'prefix') && + e.id !== '-' && e.id !== '*') { + warn('adsafe_subscript_a', e); + } + } + } + step_out(']', that); + no_space(prev_token, token); + that.first = left; + that.second = e; + return that; + }, true); + + prefix('[', function (that) { + that.first = []; + step_in('array'); + while (next_token.id !== '(end)') { + while (next_token.id === ',') { + warn('unexpected_a', next_token); + advance(','); + } + if (next_token.id === ']') { + break; + } + indent.wrap = false; + edge(); + that.first.push(expression(10)); + if (next_token.id === ',') { + comma(); + if (next_token.id === ']' && !option.es5) { + warn('unexpected_a', token); + break; + } + } else { + break; + } + } + step_out(']', that); + return that; + }, 170); + + + function property_name() { + var id = optional_identifier(); + if (!id) { + if (next_token.id === '(string)') { + id = next_token.string; + if (option.safe) { + if (banned[id]) { + warn('adsafe_a'); + } else if (id.charAt(0) === '_' || + id.charAt(id.length - 1) === '_') { + warn('dangling_a'); + } + } + advance(); + } else if (next_token.id === '(number)') { + id = next_token.number.toString(); + advance(); + } + } + return id; + } + + + + assignop('='); + assignop('+=', '+'); + assignop('-=', '-'); + assignop('*=', '*'); + assignop('/=', '/').nud = function () { + stop('slash_equal'); + }; + assignop('%=', '%'); + assignop('&=', '&'); + assignop('|=', '|'); + assignop('^=', '^'); + assignop('<<=', '<<'); + assignop('>>=', '>>'); + assignop('>>>=', '>>>'); + + function function_params() { + var id, paren = next_token, params = []; + advance('('); + token.function = funct; + step_in(); + no_space(); + if (next_token.id === ')') { + no_space(); + step_out(')', paren); + return params; + } + for (;;) { + edge(); + id = identifier(); + params.push(token); + add_label(token, option.unparam ? 'parameter' : 'unparam'); + if (next_token.id === ',') { + comma(); + } else { + no_space(); + step_out(')', paren); + return params; + } + } + } + + function do_function(func, name) { + var old_funct = funct, + old_option = option, + old_scope = scope; + funct = { + '(name)' : name || '\'' + (anonname || '').replace(nx, sanitize) + '\'', + '(line)' : next_token.line, + '(context)' : old_funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(token)' : func, + '(level)' : old_funct['(level)'] + 1 + }; + func.function = funct; + option = Object.create(old_option); + scope = Object.create(old_scope); + functions.push(funct); + func.name = name; + func.function = funct; + if (name) { + add_label(func, 'function', name); + } + func.writeable = false; + func.first = funct['(params)'] = function_params(); + one_space(); + func.block = block(false); + if (funct['(arguments)']) { + func.first.forEach(function (value) { + if (value.assign) { + warn('parameter_arguments_a', value, value.string); + } + }); + } + funct = old_funct; + option = old_option; + scope = old_scope; + } + + prefix('{', function (that) { + var get, i, j, name, p, set, seen = {}; + that.first = []; + step_in(); + while (next_token.id !== '}') { + indent.wrap = false; + +// JSLint recognizes the ES5 extension for get/set in object literals, +// but requires that they be used in pairs. + + edge(); + if (next_token.string === 'get' && peek().id !== ':') { + if (!option.es5) { + warn('es5'); + } + get = next_token; + advance('get'); + one_space_only(); + name = next_token; + i = property_name(); + if (!i) { + stop('missing_property'); + } + get.string = ''; + do_function(get); + if (funct['(loopage)']) { + warn('function_loop', get); + } + p = get.first; + if (p && p.length) { + warn('parameter_a_get_b', p[0], p[0].string, i); + } + comma(); + set = next_token; + spaces(); + edge(); + advance('set'); + set.string = ''; + one_space_only(); + j = property_name(); + if (i !== j) { + stop('expected_a_b', token, i, j || next_token.string); + } + do_function(set); + if (set.block.length === 0) { + warn('missing_a', token, 'throw'); + } + p = set.first; + if (!p || p.length !== 1) { + stop('parameter_set_a', set, 'value'); + } else if (p[0].string !== 'value') { + stop('expected_a_b', p[0], 'value', p[0].string); + } + name.first = [get, set]; + } else { + name = next_token; + i = property_name(); + if (typeof i !== 'string') { + stop('missing_property'); + } + advance(':'); + spaces(); + name.first = expression(10); + } + that.first.push(name); + if (seen[i] === true) { + warn('duplicate_a', next_token, i); + } + seen[i] = true; + tally_property(i); + if (next_token.id !== ',') { + break; + } + for (;;) { + comma(); + if (next_token.id !== ',') { + break; + } + warn('unexpected_a', next_token); + } + if (next_token.id === '}' && !option.es5) { + warn('unexpected_a', token); + } + } + step_out('}', that); + return that; + }); + + stmt('{', function () { + warn('statement_block'); + this.arity = 'statement'; + this.block = statements(); + this.disrupt = this.block.disrupt; + advance('}', this); + return this; + }); + + stmt('/*global', directive); + stmt('/*globals', directive); + stmt('/*jslint', directive); + stmt('/*member', directive); + stmt('/*members', directive); + stmt('/*property', directive); + stmt('/*properties', directive); + + stmt('var', function () { + +// JavaScript does not have block scope. It only has function scope. So, +// declaring a variable in a block can have unexpected consequences. + +// var.first will contain an array, the array containing name tokens +// and assignment tokens. + + var assign, id, name; + + if (funct['(vars)'] && !option.vars) { + warn('combine_var'); + } else if (funct !== global_funct) { + funct['(vars)'] = true; + } + this.arity = 'statement'; + this.first = []; + step_in('var'); + for (;;) { + name = next_token; + id = identifier(true); + add_label(name, 'becoming'); + + if (next_token.id === '=') { + assign = next_token; + assign.first = name; + spaces(); + advance('='); + spaces(); + if (next_token.id === 'undefined') { + warn('unnecessary_initialize', token, id); + } + if (peek(0).id === '=' && next_token.identifier) { + stop('var_a_not'); + } + assign.second = expression(0); + assign.arity = 'infix'; + this.first.push(assign); + } else { + this.first.push(name); + } + if (funct[id] === 'becoming') { + funct[id] = 'unused'; + } + if (next_token.id !== ',') { + break; + } + comma(); + indent.wrap = false; + if (var_mode && next_token.line === token.line && + this.first.length === 1) { + var_mode = null; + indent.open = false; + indent.at -= option.indent; + } + spaces(); + edge(); + } + var_mode = null; + step_out(); + return this; + }); + + stmt('function', function () { + one_space(); + if (in_block) { + warn('function_block', token); + } + var name = next_token, + id = identifier(true); + add_label(name, 'unction'); + no_space(); + this.arity = 'statement'; + do_function(this, id); + if (next_token.id === '(' && next_token.line === token.line) { + stop('function_statement'); + } + return this; + }); + + prefix('function', function (that) { + if (!option.anon) { + one_space(); + } + var id = optional_identifier(true); + if (id) { + no_space(); + } else { + id = ''; + } + do_function(that, id); + if (funct['(loopage)']) { + warn('function_loop'); + } + switch (next_token.id) { + case ';': + case '(': + case ')': + case ',': + case ']': + case '}': + case ':': + break; + case '.': + if (peek().string !== 'bind' || peek(1).id !== '(') { + warn('unexpected_a'); + } + break; + default: + stop('unexpected_a'); + } + that.arity = 'function'; + return that; + }); + + stmt('if', function () { + var paren = next_token; + one_space(); + advance('('); + step_in('control'); + no_space(); + edge(); + this.arity = 'statement'; + this.first = expected_condition(expected_relation(expression(0))); + no_space(); + step_out(')', paren); + one_space(); + this.block = block(true); + if (next_token.id === 'else') { + one_space(); + advance('else'); + one_space(); + this.else = next_token.id === 'if' || next_token.id === 'switch' + ? statement(true) + : block(true); + if (this.else.disrupt && this.block.disrupt) { + this.disrupt = true; + } + } + return this; + }); + + stmt('try', function () { + +// try.first The catch variable +// try.second The catch clause +// try.third The finally clause +// try.block The try block + + var exception_variable, old_scope, paren; + if (option.adsafe) { + warn('adsafe_a', this); + } + one_space(); + this.arity = 'statement'; + this.block = block(false); + if (next_token.id === 'catch') { + one_space(); + advance('catch'); + one_space(); + paren = next_token; + advance('('); + step_in('control'); + no_space(); + edge(); + old_scope = scope; + scope = Object.create(old_scope); + exception_variable = next_token.string; + this.first = exception_variable; + if (!next_token.identifier) { + warn('expected_identifier_a', next_token); + } else { + add_label(next_token, 'exception'); + } + advance(); + no_space(); + step_out(')', paren); + one_space(); + this.second = block(false); + scope = old_scope; + } + if (next_token.id === 'finally') { + one_space(); + advance('finally'); + one_space(); + this.third = block(false); + } else if (!this.second) { + stop('expected_a_b', next_token, 'catch', artifact()); + } + return this; + }); + + labeled_stmt('while', function () { + one_space(); + var paren = next_token; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + step_in('control'); + no_space(); + edge(); + this.arity = 'statement'; + this.first = expected_relation(expression(0)); + if (this.first.id !== 'true') { + expected_condition(this.first, bundle.unexpected_a); + } + no_space(); + step_out(')', paren); + one_space(); + this.block = block(true); + if (this.block.disrupt) { + warn('strange_loop', prev_token); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + + reserve('with'); + + labeled_stmt('switch', function () { + +// switch.first the switch expression +// switch.second the array of cases. A case is 'case' or 'default' token: +// case.first the array of case expressions +// case.second the array of statements +// If all of the arrays of statements are disrupt, then the switch is disrupt. + + var cases = [], + old_in_block = in_block, + particular, + that = token, + the_case = next_token, + unbroken = true; + + function find_duplicate_case(value) { + if (are_similar(particular, value)) { + warn('duplicate_a', value); + } + } + + funct['(breakage)'] += 1; + one_space(); + advance('('); + no_space(); + step_in(); + this.arity = 'statement'; + this.first = expected_condition(expected_relation(expression(0))); + no_space(); + step_out(')', the_case); + one_space(); + advance('{'); + step_in(); + in_block = true; + this.second = []; + if (that.from !== next_token.from && !option.white) { + warn('expected_a_at_b_c', next_token, next_token.string, that.from, next_token.from); + } + while (next_token.id === 'case') { + the_case = next_token; + cases.forEach(find_duplicate_case); + the_case.first = []; + the_case.arity = 'case'; + spaces(); + edge('case'); + advance('case'); + for (;;) { + one_space(); + particular = expression(0); + cases.forEach(find_duplicate_case); + cases.push(particular); + the_case.first.push(particular); + if (particular.id === 'NaN') { + warn('unexpected_a', particular); + } + no_space_only(); + advance(':'); + if (next_token.id !== 'case') { + break; + } + spaces(); + edge('case'); + advance('case'); + } + spaces(); + the_case.second = statements(); + if (the_case.second && the_case.second.length > 0) { + particular = the_case.second[the_case.second.length - 1]; + if (particular.disrupt) { + if (particular.id === 'break') { + unbroken = false; + } + } else { + warn('missing_a_after_b', next_token, 'break', 'case'); + } + } else { + warn('empty_case'); + } + this.second.push(the_case); + } + if (this.second.length === 0) { + warn('missing_a', next_token, 'case'); + } + if (next_token.id === 'default') { + spaces(); + the_case = next_token; + the_case.arity = 'case'; + edge('case'); + advance('default'); + no_space_only(); + advance(':'); + spaces(); + the_case.second = statements(); + if (the_case.second && the_case.second.length > 0) { + particular = the_case.second[the_case.second.length - 1]; + if (unbroken && particular.disrupt && particular.id !== 'break') { + this.disrupt = true; + } + } + this.second.push(the_case); + } + funct['(breakage)'] -= 1; + spaces(); + step_out('}', this); + in_block = old_in_block; + return this; + }); + + stmt('debugger', function () { + if (!option.debug) { + warn('unexpected_a', this); + } + this.arity = 'statement'; + return this; + }); + + labeled_stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + one_space(); + this.arity = 'statement'; + this.block = block(true); + if (this.block.disrupt) { + warn('strange_loop', prev_token); + } + one_space(); + advance('while'); + var paren = next_token; + one_space(); + advance('('); + step_in(); + no_space(); + edge(); + this.first = expected_condition(expected_relation(expression(0)), bundle.unexpected_a); + no_space(); + step_out(')', paren); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + + labeled_stmt('for', function () { + + var blok, filter, ok = false, paren = next_token, value; + this.arity = 'statement'; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + if (next_token.id === ';') { + no_space(); + advance(';'); + no_space(); + advance(';'); + no_space(); + advance(')'); + blok = block(true); + } else { + step_in('control'); + spaces(this, paren); + no_space(); + if (next_token.id === 'var') { + stop('move_var'); + } + edge(); + if (peek(0).id === 'in') { + this.forin = true; + value = next_token; + switch (funct[value.string]) { + case 'unused': + funct[value.string] = 'var'; + break; + case 'closure': + case 'var': + break; + default: + warn('bad_in_a', value); + } + advance(); + advance('in'); + this.first = value; + this.second = expression(20); + step_out(')', paren); + blok = block(true); + if (!option.forin) { + if (blok.length === 1 && typeof blok[0] === 'object' && + blok[0].string === 'if' && !blok[0].else) { + filter = blok[0].first; + while (filter.id === '&&') { + filter = filter.first; + } + switch (filter.id) { + case '===': + case '!==': + ok = filter.first.id === '[' + ? filter.first.first.string === this.second.string && + filter.first.second.string === this.first.string + : filter.first.id === 'typeof' && + filter.first.first.id === '[' && + filter.first.first.first.string === this.second.string && + filter.first.first.second.string === this.first.string; + break; + case '(': + ok = filter.first.id === '.' && (( + filter.first.first.string === this.second.string && + filter.first.second.string === 'hasOwnProperty' && + filter.second[0].string === this.first.string + ) || ( + filter.first.first.string === 'ADSAFE' && + filter.first.second.string === 'has' && + filter.second[0].string === this.second.string && + filter.second[1].string === this.first.string + ) || ( + filter.first.first.id === '.' && + filter.first.first.first.id === '.' && + filter.first.first.first.first.string === 'Object' && + filter.first.first.first.second.string === 'prototype' && + filter.first.first.second.string === 'hasOwnProperty' && + filter.first.second.string === 'call' && + filter.second[0].string === this.second.string && + filter.second[1].string === this.first.string + )); + break; + } + } + if (!ok) { + warn('for_if', this); + } + } + } else { + edge(); + this.first = []; + for (;;) { + this.first.push(expression(0, 'for')); + if (next_token.id !== ',') { + break; + } + comma(); + } + semicolon(); + edge(); + this.second = expected_relation(expression(0)); + if (this.second.id !== 'true') { + expected_condition(this.second, bundle.unexpected_a); + } + semicolon(token); + if (next_token.id === ';') { + stop('expected_a_b', next_token, ')', ';'); + } + this.third = []; + edge(); + for (;;) { + this.third.push(expression(0, 'for')); + if (next_token.id !== ',') { + break; + } + comma(); + } + no_space(); + step_out(')', paren); + one_space(); + blok = block(true); + } + } + if (blok.disrupt) { + warn('strange_loop', prev_token); + } + this.block = blok; + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + + disrupt_stmt('break', function () { + var label = next_token.string; + this.arity = 'statement'; + if (funct['(breakage)'] === 0) { + warn('unexpected_a', this); + } + if (next_token.identifier && token.line === next_token.line) { + one_space_only(); + if (funct[label] !== 'label') { + warn('not_a_label', next_token); + } else if (scope[label].function !== funct) { + warn('not_a_scope', next_token); + } + this.first = next_token; + advance(); + } + return this; + }); + + disrupt_stmt('continue', function () { + if (!option.continue) { + warn('unexpected_a', this); + } + var label = next_token.string; + this.arity = 'statement'; + if (funct['(breakage)'] === 0) { + warn('unexpected_a', this); + } + if (next_token.identifier && token.line === next_token.line) { + one_space_only(); + if (funct[label] !== 'label') { + warn('not_a_label', next_token); + } else if (scope[label].function !== funct) { + warn('not_a_scope', next_token); + } + this.first = next_token; + advance(); + } + return this; + }); + + disrupt_stmt('return', function () { + if (funct === global_funct && xmode !== 'scriptstring') { + warn('unexpected_a', this); + } + this.arity = 'statement'; + if (next_token.id !== ';' && next_token.line === token.line) { + one_space_only(); + if (next_token.id === '/' || next_token.id === '(regexp)') { + warn('wrap_regexp'); + } + this.first = expression(0); + if (this.first.assign) { + warn('unexpected_a', this.first); + } + } + if (peek(0).id === '}' && peek(1).id === 'else') { + warn('unexpected_else', this); + } + return this; + }); + + disrupt_stmt('throw', function () { + this.arity = 'statement'; + one_space_only(); + this.first = expression(20); + return this; + }); + + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + +// Harmony reserved words + + reserve('implements'); + reserve('interface'); + reserve('let'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + reserve('yield'); + + +// Parse JSON + + function json_value() { + + function json_object() { + var brace = next_token, object = {}; + advance('{'); + if (next_token.id !== '}') { + while (next_token.id !== '(end)') { + while (next_token.id === ',') { + warn('unexpected_a', next_token); + advance(','); + } + if (next_token.id !== '(string)') { + warn('expected_string_a'); + } + if (object[next_token.string] === true) { + warn('duplicate_a'); + } else if (next_token.string === '__proto__') { + warn('dangling_a'); + } else { + object[next_token.string] = true; + } + advance(); + advance(':'); + json_value(); + if (next_token.id !== ',') { + break; + } + advance(','); + if (next_token.id === '}') { + warn('unexpected_a', token); + break; + } + } + } + advance('}', brace); + } + + function json_array() { + var bracket = next_token; + advance('['); + if (next_token.id !== ']') { + while (next_token.id !== '(end)') { + while (next_token.id === ',') { + warn('unexpected_a', next_token); + advance(','); + } + json_value(); + if (next_token.id !== ',') { + break; + } + advance(','); + if (next_token.id === ']') { + warn('unexpected_a', token); + break; + } + } + } + advance(']', bracket); + } + + switch (next_token.id) { + case '{': + json_object(); + break; + case '[': + json_array(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + no_space_only(); + advance('(number)'); + break; + default: + stop('unexpected_a'); + } + } + + +// CSS parsing. + + function css_name() { + if (next_token.identifier) { + advance(); + return true; + } + } + + + function css_number() { + if (next_token.id === '-') { + advance('-'); + no_space_only(); + } + if (next_token.id === '(number)') { + advance('(number)'); + return true; + } + } + + + function css_string() { + if (next_token.id === '(string)') { + advance(); + return true; + } + } + + function css_color() { + var i, number, paren, value; + if (next_token.identifier) { + value = next_token.string; + if (value === 'rgb' || value === 'rgba') { + advance(); + paren = next_token; + advance('('); + for (i = 0; i < 3; i += 1) { + if (i) { + comma(); + } + number = next_token.number; + if (next_token.id !== '(number)' || number < 0) { + warn('expected_positive_a', next_token); + advance(); + } else { + advance(); + if (next_token.id === '%') { + advance('%'); + if (number > 100) { + warn('expected_percent_a', token, number); + } + } else { + if (number > 255) { + warn('expected_small_a', token, number); + } + } + } + } + if (value === 'rgba') { + comma(); + number = next_token.number; + if (next_token.id !== '(number)' || number < 0 || number > 1) { + warn('expected_fraction_a', next_token); + } + advance(); + if (next_token.id === '%') { + warn('unexpected_a'); + advance('%'); + } + } + advance(')', paren); + return true; + } + if (css_colorData[next_token.string] === true) { + advance(); + return true; + } + } else if (next_token.id === '(color)') { + advance(); + return true; + } + return false; + } + + + function css_length() { + if (next_token.id === '-') { + advance('-'); + no_space_only(); + } + if (next_token.id === '(number)') { + advance(); + if (next_token.id !== '(string)' && + css_lengthData[next_token.string] === true) { + no_space_only(); + advance(); + } else if (+token.number !== 0) { + warn('expected_linear_a'); + } + return true; + } + return false; + } + + + function css_line_height() { + if (next_token.id === '-') { + advance('-'); + no_space_only(); + } + if (next_token.id === '(number)') { + advance(); + if (next_token.id !== '(string)' && + css_lengthData[next_token.string] === true) { + no_space_only(); + advance(); + } + return true; + } + return false; + } + + + function css_width() { + if (next_token.identifier) { + switch (next_token.string) { + case 'thin': + case 'medium': + case 'thick': + advance(); + return true; + } + } else { + return css_length(); + } + } + + + function css_margin() { + if (next_token.identifier) { + if (next_token.string === 'auto') { + advance(); + return true; + } + } else { + return css_length(); + } + } + + function css_attr() { + if (next_token.identifier && next_token.string === 'attr') { + advance(); + advance('('); + if (!next_token.identifier) { + warn('expected_name_a'); + } + advance(); + advance(')'); + return true; + } + return false; + } + + + function css_comma_list() { + while (next_token.id !== ';') { + if (!css_name() && !css_string()) { + warn('expected_name_a'); + } + if (next_token.id !== ',') { + return true; + } + comma(); + } + } + + + function css_counter() { + if (next_token.identifier && next_token.string === 'counter') { + advance(); + advance('('); + advance(); + if (next_token.id === ',') { + comma(); + if (next_token.id !== '(string)') { + warn('expected_string_a'); + } + advance(); + } + advance(')'); + return true; + } + if (next_token.identifier && next_token.string === 'counters') { + advance(); + advance('('); + if (!next_token.identifier) { + warn('expected_name_a'); + } + advance(); + if (next_token.id === ',') { + comma(); + if (next_token.id !== '(string)') { + warn('expected_string_a'); + } + advance(); + } + if (next_token.id === ',') { + comma(); + if (next_token.id !== '(string)') { + warn('expected_string_a'); + } + advance(); + } + advance(')'); + return true; + } + return false; + } + + + function css_radius() { + return css_length() && (next_token.id !== '(number)' || css_length()); + } + + + function css_shadow() { + for (;;) { + if (next_token.string === 'inset') { + advance(); + } + for (;;) { + if (!css_length()) { + break; + } + } + css_color(); + if (next_token.id !== ',') { + break; + } + advance(','); + } + return true; + } + + + function css_shape() { + var i; + if (next_token.identifier && next_token.string === 'rect') { + advance(); + advance('('); + for (i = 0; i < 4; i += 1) { + if (!css_length()) { + warn('expected_number_a'); + break; + } + } + advance(')'); + return true; + } + return false; + } + + + function css_url() { + var c, url; + if (next_token.identifier && next_token.string === 'url') { + next_token = lex.range('(', ')'); + url = next_token.string; + c = url.charAt(0); + if (c === '"' || c === '\'') { + if (url.slice(-1) !== c) { + warn('bad_url_a'); + } else { + url = url.slice(1, -1); + if (url.indexOf(c) >= 0) { + warn('bad_url_a'); + } + } + } + if (!url) { + warn('missing_url'); + } + if (ux.test(url)) { + stop('bad_url_a'); + } + urls.push(url); + advance(); + return true; + } + return false; + } + + + css_any = [css_url, function () { + for (;;) { + if (next_token.identifier) { + switch (next_token.string.toLowerCase()) { + case 'url': + css_url(); + break; + case 'expression': + warn('unexpected_a'); + advance(); + break; + default: + advance(); + } + } else { + if (next_token.id === ';' || next_token.id === '!' || + next_token.id === '(end)' || next_token.id === '}') { + return true; + } + advance(); + } + } + }]; + + + function font_face() { + advance_identifier('font-family'); + advance(':'); + if (!css_name() && !css_string()) { + stop('expected_name_a'); + } + semicolon(); + advance_identifier('src'); + advance(':'); + while (true) { + if (next_token.string === 'local') { + advance_identifier('local'); + advance('('); + if (ux.test(next_token.string)) { + stop('bad_url_a'); + } + + if (!css_name() && !css_string()) { + stop('expected_name_a'); + } + advance(')'); + } else if (!css_url()) { + stop('expected_a_b', next_token, 'url', artifact()); + } + if (next_token.id !== ',') { + break; + } + comma(); + } + semicolon(); + } + + + css_border_style = [ + 'none', 'dashed', 'dotted', 'double', 'groove', + 'hidden', 'inset', 'outset', 'ridge', 'solid' + ]; + + css_break = [ + 'auto', 'always', 'avoid', 'left', 'right' + ]; + + css_media = { + 'all': true, + 'braille': true, + 'embossed': true, + 'handheld': true, + 'print': true, + 'projection': true, + 'screen': true, + 'speech': true, + 'tty': true, + 'tv': true + }; + + css_overflow = [ + 'auto', 'hidden', 'scroll', 'visible' + ]; + + css_attribute_data = { + background: [ + true, 'background-attachment', 'background-color', + 'background-image', 'background-position', 'background-repeat' + ], + 'background-attachment': ['scroll', 'fixed'], + 'background-color': ['transparent', css_color], + 'background-image': ['none', css_url], + 'background-position': [ + 2, [css_length, 'top', 'bottom', 'left', 'right', 'center'] + ], + 'background-repeat': [ + 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' + ], + 'border': [true, 'border-color', 'border-style', 'border-width'], + 'border-bottom': [ + true, 'border-bottom-color', 'border-bottom-style', + 'border-bottom-width' + ], + 'border-bottom-color': css_color, + 'border-bottom-left-radius': css_radius, + 'border-bottom-right-radius': css_radius, + 'border-bottom-style': css_border_style, + 'border-bottom-width': css_width, + 'border-collapse': ['collapse', 'separate'], + 'border-color': ['transparent', 4, css_color], + 'border-left': [ + true, 'border-left-color', 'border-left-style', 'border-left-width' + ], + 'border-left-color': css_color, + 'border-left-style': css_border_style, + 'border-left-width': css_width, + 'border-radius': function () { + function count(separator) { + var n = 1; + if (separator) { + advance(separator); + } + if (!css_length()) { + return false; + } + while (next_token.id === '(number)') { + if (!css_length()) { + return false; + } + n += 1; + } + if (n > 4) { + warn('bad_style'); + } + return true; + } + + return count() && (next_token.id !== '/' || count('/')); + }, + 'border-right': [ + true, 'border-right-color', 'border-right-style', + 'border-right-width' + ], + 'border-right-color': css_color, + 'border-right-style': css_border_style, + 'border-right-width': css_width, + 'border-spacing': [2, css_length], + 'border-style': [4, css_border_style], + 'border-top': [ + true, 'border-top-color', 'border-top-style', 'border-top-width' + ], + 'border-top-color': css_color, + 'border-top-left-radius': css_radius, + 'border-top-right-radius': css_radius, + 'border-top-style': css_border_style, + 'border-top-width': css_width, + 'border-width': [4, css_width], + bottom: [css_length, 'auto'], + 'box-shadow': ['none', css_shadow], + 'caption-side' : ['bottom', 'left', 'right', 'top'], + clear: ['both', 'left', 'none', 'right'], + clip: [css_shape, 'auto'], + color: css_color, + content: [ + 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', + css_string, css_url, css_counter, css_attr + ], + 'counter-increment': [ + css_name, 'none' + ], + 'counter-reset': [ + css_name, 'none' + ], + cursor: [ + css_url, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', + 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', + 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' + ], + direction: ['ltr', 'rtl'], + display: [ + 'block', 'compact', 'inline', 'inline-block', 'inline-table', + 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', + 'table-cell', 'table-column', 'table-column-group', + 'table-footer-group', 'table-header-group', 'table-row', + 'table-row-group' + ], + 'empty-cells': ['show', 'hide'], + 'float': ['left', 'none', 'right'], + font: [ + 'caption', 'icon', 'menu', 'message-box', 'small-caption', + 'status-bar', true, 'font-size', 'font-style', 'font-weight', + 'font-family' + ], + 'font-family': css_comma_list, + 'font-size': [ + 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', + 'xx-large', 'larger', 'smaller', css_length + ], + 'font-size-adjust': ['none', css_number], + 'font-stretch': [ + 'normal', 'wider', 'narrower', 'ultra-condensed', + 'extra-condensed', 'condensed', 'semi-condensed', + 'semi-expanded', 'expanded', 'extra-expanded' + ], + 'font-style': [ + 'normal', 'italic', 'oblique' + ], + 'font-variant': [ + 'normal', 'small-caps' + ], + 'font-weight': [ + 'normal', 'bold', 'bolder', 'lighter', css_number + ], + height: [css_length, 'auto'], + left: [css_length, 'auto'], + 'letter-spacing': ['normal', css_length], + 'line-height': ['normal', css_line_height], + 'list-style': [ + true, 'list-style-image', 'list-style-position', 'list-style-type' + ], + 'list-style-image': ['none', css_url], + 'list-style-position': ['inside', 'outside'], + 'list-style-type': [ + 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', + 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', + 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', + 'hiragana-iroha', 'katakana-oroha', 'none' + ], + margin: [4, css_margin], + 'margin-bottom': css_margin, + 'margin-left': css_margin, + 'margin-right': css_margin, + 'margin-top': css_margin, + 'marker-offset': [css_length, 'auto'], + 'max-height': [css_length, 'none'], + 'max-width': [css_length, 'none'], + 'min-height': css_length, + 'min-width': css_length, + opacity: css_number, + outline: [true, 'outline-color', 'outline-style', 'outline-width'], + 'outline-color': ['invert', css_color], + 'outline-style': [ + 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', + 'outset', 'ridge', 'solid' + ], + 'outline-width': css_width, + overflow: css_overflow, + 'overflow-x': css_overflow, + 'overflow-y': css_overflow, + padding: [4, css_length], + 'padding-bottom': css_length, + 'padding-left': css_length, + 'padding-right': css_length, + 'padding-top': css_length, + 'page-break-after': css_break, + 'page-break-before': css_break, + position: ['absolute', 'fixed', 'relative', 'static'], + quotes: [8, css_string], + right: [css_length, 'auto'], + 'table-layout': ['auto', 'fixed'], + 'text-align': ['center', 'justify', 'left', 'right'], + 'text-decoration': [ + 'none', 'underline', 'overline', 'line-through', 'blink' + ], + 'text-indent': css_length, + 'text-shadow': ['none', 4, [css_color, css_length]], + 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], + top: [css_length, 'auto'], + 'unicode-bidi': ['normal', 'embed', 'bidi-override'], + 'vertical-align': [ + 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', + 'text-bottom', css_length + ], + visibility: ['visible', 'hidden', 'collapse'], + 'white-space': [ + 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' + ], + width: [css_length, 'auto'], + 'word-spacing': ['normal', css_length], + 'word-wrap': ['break-word', 'normal'], + 'z-index': ['auto', css_number] + }; + + function style_attribute() { + var v; + while (next_token.id === '*' || next_token.id === '#' || + next_token.string === '_') { + if (!option.css) { + warn('unexpected_a'); + } + advance(); + } + if (next_token.id === '-') { + if (!option.css) { + warn('unexpected_a'); + } + advance('-'); + if (!next_token.identifier) { + warn('expected_nonstandard_style_attribute'); + } + advance(); + return css_any; + } + if (!next_token.identifier) { + warn('expected_style_attribute'); + } else { + if (Object.prototype.hasOwnProperty.call(css_attribute_data, + next_token.string)) { + v = css_attribute_data[next_token.string]; + } else { + v = css_any; + if (!option.css) { + warn('unrecognized_style_attribute_a'); + } + } + } + advance(); + return v; + } + + + function style_value(v) { + var i = 0, + n, + once, + match, + round, + start = 0, + vi; + switch (typeof v) { + case 'function': + return v(); + case 'string': + if (next_token.identifier && next_token.string === v) { + advance(); + return true; + } + return false; + } + for (;;) { + if (i >= v.length) { + return false; + } + vi = v[i]; + i += 1; + if (typeof vi === 'boolean') { + break; + } else if (typeof vi === 'number') { + n = vi; + vi = v[i]; + i += 1; + } else { + n = 1; + } + match = false; + while (n > 0) { + if (style_value(vi)) { + match = true; + n -= 1; + } else { + break; + } + } + if (match) { + return true; + } + } + start = i; + once = []; + for (;;) { + round = false; + for (i = start; i < v.length; i += 1) { + if (!once[i]) { + if (style_value(css_attribute_data[v[i]])) { + match = true; + round = true; + once[i] = true; + break; + } + } + } + if (!round) { + return match; + } + } + } + + function style_child() { + if (next_token.id === '(number)') { + advance(); + if (next_token.string === 'n' && next_token.identifier) { + no_space_only(); + advance(); + if (next_token.id === '+') { + no_space_only(); + advance('+'); + no_space_only(); + advance('(number)'); + } + } + return; + } + if (next_token.identifier && + (next_token.string === 'odd' || next_token.string === 'even')) { + advance(); + return; + } + warn('unexpected_a'); + } + + function substyle() { + var v; + for (;;) { + if (next_token.id === '}' || next_token.id === '(end)' || + (xquote && next_token.id === xquote)) { + return; + } + v = style_attribute(); + advance(':'); + if (next_token.identifier && next_token.string === 'inherit') { + advance(); + } else { + if (!style_value(v)) { + warn('unexpected_a'); + advance(); + } + } + if (next_token.id === '!') { + advance('!'); + no_space_only(); + if (next_token.identifier && next_token.string === 'important') { + advance(); + } else { + warn('expected_a_b', + next_token, 'important', artifact()); + } + } + if (next_token.id === '}' || next_token.id === xquote) { + warn('expected_a_b', next_token, ';', artifact()); + } else { + semicolon(); + } + } + } + + function style_selector() { + if (next_token.identifier) { + if (!Object.prototype.hasOwnProperty.call(html_tag, + next_token.string)) { + warn('expected_tagname_a'); + } + advance(); + } else { + switch (next_token.id) { + case '>': + case '+': + advance(); + style_selector(); + break; + case ':': + advance(':'); + switch (next_token.string) { + case 'active': + case 'after': + case 'before': + case 'checked': + case 'disabled': + case 'empty': + case 'enabled': + case 'first-child': + case 'first-letter': + case 'first-line': + case 'first-of-type': + case 'focus': + case 'hover': + case 'last-child': + case 'last-of-type': + case 'link': + case 'only-of-type': + case 'root': + case 'target': + case 'visited': + advance_identifier(next_token.string); + break; + case 'lang': + advance_identifier('lang'); + advance('('); + if (!next_token.identifier) { + warn('expected_lang_a'); + } + advance(')'); + break; + case 'nth-child': + case 'nth-last-child': + case 'nth-last-of-type': + case 'nth-of-type': + advance_identifier(next_token.string); + advance('('); + style_child(); + advance(')'); + break; + case 'not': + advance_identifier('not'); + advance('('); + if (next_token.id === ':' && peek(0).string === 'not') { + warn('not'); + } + style_selector(); + advance(')'); + break; + default: + warn('expected_pseudo_a'); + } + break; + case '#': + advance('#'); + if (!next_token.identifier) { + warn('expected_id_a'); + } + advance(); + break; + case '*': + advance('*'); + break; + case '.': + advance('.'); + if (!next_token.identifier) { + warn('expected_class_a'); + } + advance(); + break; + case '[': + advance('['); + if (!next_token.identifier) { + warn('expected_attribute_a'); + } + advance(); + if (next_token.id === '=' || next_token.string === '~=' || + next_token.string === '$=' || + next_token.string === '|=' || + next_token.id === '*=' || + next_token.id === '^=') { + advance(); + if (next_token.id !== '(string)') { + warn('expected_string_a'); + } + advance(); + } + advance(']'); + break; + default: + stop('expected_selector_a'); + } + } + } + + function style_pattern() { + if (next_token.id === '{') { + warn('expected_style_pattern'); + } + for (;;) { + style_selector(); + if (next_token.id === '= 0) { + warn('unexpected_char_a_b', token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'class' || a === 'type' || a === 'name') { + x = v.search(qx); + if (x >= 0) { + warn('unexpected_char_a_b', token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'href' || a === 'background' || + a === 'content' || a === 'data' || + a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { + if (option.safe && ux.test(v)) { + stop('bad_url_a', next_token, v); + } + urls.push(v); + } else if (a === 'for') { + if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warn('adsafe_prefix_a', next_token, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warn('adsafe_bad_id'); + } + } else { + warn('adsafe_bad_id'); + } + } + } else if (a === 'name') { + if (option.adsafe && v.indexOf('_') >= 0) { + warn('adsafe_name_a', next_token, v); + } + } + } + + function do_tag(tag, name, attribute) { + var i, script, x; + src = false; + if (stack.length > 0) { + if (name === 'html') { + stop('unexpected_a', token, name); + } + x = tag.parent; + if (x) { + if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { + stop('tag_a_in_b', token, name, x); + } + } else if (x !== false && !option.adsafe && !option.fragment) { + i = stack.length; + do { + if (i <= 0) { + stop('tag_a_in_b', token, name, 'body'); + } + i -= 1; + } while (stack[i].name !== 'body'); + } + } + switch (name) { + case 'div': + if (option.adsafe && stack.length === 1 && !adsafe_id) { + warn('adsafe_missing_id'); + } + break; + case 'script': + xmode = 'script'; + advance('>'); + if (attribute.lang) { + warn('lang', token); + } + if (option.adsafe && stack.length !== 1) { + warn('adsafe_placement', token); + } + if (attribute.src) { + if (option.adsafe && (!adsafe_may || !approved[attribute.src])) { + warn('adsafe_source', token); + } + } else { + step_in(next_token.from); + edge(); + use_strict(); + adsafe_top = true; + script = statements(); + +// JSLint is also the static analyzer for ADsafe. See www.ADsafe.org. + + if (option.adsafe) { + if (adsafe_went) { + stop('adsafe_script', token); + } + if (script.length !== 1 || + aint(script[0], 'id', '(') || + aint(script[0].first, 'id', '.') || + aint(script[0].first.first, 'string', 'ADSAFE') || + aint(script[0].second[0], 'string', adsafe_id)) { + stop('adsafe_id_go'); + } + switch (script[0].first.second.string) { + case 'id': + if (adsafe_may || adsafe_went || + script[0].second.length !== 1) { + stop('adsafe_id', next_token); + } + adsafe_may = true; + break; + case 'go': + if (adsafe_went) { + stop('adsafe_go'); + } + if (script[0].second.length !== 2 || + aint(script[0].second[1], 'id', 'function') || + !script[0].second[1].first || + aint(script[0].second[1].first[0], 'string', 'dom') || + script[0].second[1].first.length > 2 || + (script[0].second[1].first.length === 2 && + aint(script[0].second[1].first[1], 'string', 'lib'))) { + stop('adsafe_go', next_token); + } + adsafe_went = true; + break; + default: + stop('adsafe_id_go'); + } + } + indent = null; + } + xmode = 'html'; + advance(''); + styles(); + xmode = 'html'; + advance(''; + } + + function html() { + var attribute, attributes, is_empty, name, old_white = option.white, + quote, tag_name, tag, value, wmode; + xmode = 'html'; + xquote = ''; + stack = null; + for (;;) { + switch (next_token.string) { + case '<': + xmode = 'html'; + advance('<'); + attributes = {}; + tag_name = next_token; + name = tag_name.string; + advance_identifier(name); + tag_name.name = name; + if (!stack) { + stack = []; + do_begin(name); + } + tag = html_tag[name]; + if (typeof tag !== 'object') { + tag = {parent: false}; + warn('unrecognized_tag_a', tag_name, name === name.toLowerCase() + ? name + : name + ' (capitalization error)'); + } else { + is_empty = tag.empty; + } + tag_name.type = name; + for (;;) { + if (next_token.id === '/') { + advance('/'); + if (next_token.id !== '>') { + warn('expected_a_b', next_token, '>', artifact()); + } + break; + } + if (next_token.id && next_token.id.charAt(0) === '>') { + break; + } + if (!next_token.identifier) { + if (next_token.id === '(end)' || next_token.id === '(error)') { + warn('expected_a_b', next_token, '>', artifact()); + } + warn('bad_name_a'); + } + option.white = false; + spaces(); + attribute = next_token.string; + option.white = old_white; + advance(); + if (attribute !== attribute.toLowerCase()) { + warn('attribute_case_a', token); + } + attribute = attribute.toLowerCase(); + xquote = ''; + if (Object.prototype.hasOwnProperty.call(attributes, attribute)) { + warn('duplicate_a', token, attribute); + } + if (attribute.slice(0, 2) === 'on') { + if (!option.on) { + warn('html_handlers'); + } + xmode = 'scriptstring'; + advance('='); + quote = next_token.id; + if (quote !== '"' && quote !== '\'') { + stop('expected_a_b', next_token, '"', artifact()); + } + xquote = quote; + wmode = option.white; + option.white = true; + advance(quote); + use_strict(); + statements(); + option.white = wmode; + if (next_token.id !== quote) { + stop('expected_a_b', next_token, quote, artifact()); + } + xmode = 'html'; + xquote = ''; + advance(quote); + value = false; + } else if (attribute === 'style') { + xmode = 'scriptstring'; + advance('='); + quote = next_token.id; + if (quote !== '"' && quote !== '\'') { + stop('expected_a_b', next_token, '"', artifact()); + } + xmode = 'styleproperty'; + xquote = quote; + advance(quote); + substyle(); + xmode = 'html'; + xquote = ''; + advance(quote); + value = false; + } else { + if (next_token.id === '=') { + advance('='); + value = next_token.string; + if (!next_token.identifier && + next_token.id !== '"' && + next_token.id !== '\'' && + next_token.id !== '(string)' && + next_token.id !== '(number)' && + next_token.id !== '(color)') { + warn('expected_attribute_value_a', token, attribute); + } + advance(); + } else { + value = true; + } + } + attributes[attribute] = value; + do_attribute(attribute, value); + } + do_tag(tag, name, attributes); + if (!is_empty) { + stack.push(tag_name); + } + xmode = 'outer'; + advance('>'); + break; + case '') { + stop('expected_a_b', next_token, '>', artifact()); + } + xmode = 'outer'; + advance('>'); + break; + case '' || next_token.id === '(end)') { + break; + } + if (next_token.string.indexOf('--') >= 0) { + stop('unexpected_a', next_token, '--'); + } + if (next_token.string.indexOf('<') >= 0) { + stop('unexpected_a', next_token, '<'); + } + if (next_token.string.indexOf('>') >= 0) { + stop('unexpected_a', next_token, '>'); + } + } + xmode = 'outer'; + advance('>'); + break; + case '(end)': + if (stack.length !== 0) { + warn('missing_a', next_token, ''); + } + return; + default: + if (next_token.id === '(end)') { + stop('missing_a', next_token, + ''); + } else { + advance(); + } + } + if (stack && stack.length === 0 && (option.adsafe || + !option.fragment || next_token.id === '(end)')) { + break; + } + } + if (next_token.id !== '(end)') { + stop('unexpected_a'); + } + } + + +// The actual JSLINT function itself. + + itself = function JSLint(the_source, the_option) { + + var i, predef, tree; + JSLINT.errors = []; + JSLINT.tree = ''; + JSLINT.properties = ''; + begin = prev_token = token = next_token = + Object.create(syntax['(begin)']); + tokens = []; + predefined = {}; + add_to_predefined(standard); + property = {}; + if (the_option) { + option = Object.create(the_option); + predef = option.predef; + if (predef) { + if (Array.isArray(predef)) { + for (i = 0; i < predef.length; i += 1) { + predefined[predef[i]] = true; + } + } else if (typeof predef === 'object') { + add_to_predefined(predef); + } + } + do_safe(); + } else { + option = {}; + } + option.indent = +option.indent || 4; + option.maxerr = +option.maxerr || 50; + adsafe_id = ''; + adsafe_may = adsafe_top = adsafe_went = false; + approved = {}; + if (option.approved) { + for (i = 0; i < option.approved.length; i += 1) { + approved[option.approved[i]] = option.approved[i]; + } + } else { + approved.test = 'test'; + } + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + global_scope = scope = {}; + global_funct = funct = { + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0, + '(level)': 0 + }; + functions = [funct]; + + comments_off = false; + ids = {}; + in_block = false; + indent = null; + json_mode = false; + lookahead = []; + node_js = false; + prereg = true; + src = false; + stack = null; + strict_mode = false; + urls = []; + var_mode = null; + warnings = 0; + xmode = ''; + lex.init(the_source); + + assume(); + + try { + advance(); + if (next_token.id === '(number)') { + stop('unexpected_a'); + } else if (next_token.string.charAt(0) === '<') { + html(); + if (option.adsafe && !adsafe_went) { + warn('adsafe_go', this); + } + } else { + switch (next_token.id) { + case '{': + case '[': + json_mode = true; + json_value(); + break; + case '@': + case '*': + case '#': + case '.': + case ':': + xmode = 'style'; + advance(); + if (token.id !== '@' || !next_token.identifier || + next_token.string !== 'charset' || token.line !== 1 || + token.from !== 1) { + stop('css'); + } + advance(); + if (next_token.id !== '(string)' && + next_token.string !== 'UTF-8') { + stop('css'); + } + advance(); + semicolon(); + styles(); + break; + + default: + if (option.adsafe && option.fragment) { + stop('expected_a_b', + next_token, '
    ', artifact()); + } + +// If the first token is a semicolon, ignore it. This is sometimes used when +// files are intended to be appended to files that may be sloppy. A sloppy +// file may be depending on semicolon insertion on its last line. + + step_in(1); + if (next_token.id === ';' && !node_js) { + semicolon(); + } + adsafe_top = true; + tree = statements(); + begin.first = tree; + itself.tree = begin; + if (option.adsafe && (tree.length !== 1 || + aint(tree[0], 'id', '(') || + aint(tree[0].first, 'id', '.') || + aint(tree[0].first.first, 'string', 'ADSAFE') || + aint(tree[0].first.second, 'string', 'lib') || + tree[0].second.length !== 2 || + tree[0].second[0].id !== '(string)' || + aint(tree[0].second[1], 'id', 'function'))) { + stop('adsafe_lib'); + } + if (tree.disrupt) { + warn('weird_program', prev_token); + } + } + } + indent = null; + advance('(end)'); + itself.property = property; + } catch (e) { + if (e) { // ~~ + JSLINT.errors.push({ + reason : e.message, + line : e.line || next_token.line, + character : e.character || next_token.from + }, null); + } + } + return JSLINT.errors.length === 0; + }; + + +// Data summary. + + itself.data = function () { + var data = {functions: []}, + function_data, + globals, + i, + j, + kind, + name, + the_function, + undef = [], + unused = []; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (json_mode) { + data.json = true; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(global_scope).filter(function (value) { + return value.charAt(0) !== '(' && typeof standard[value] !== 'boolean'; + }); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + the_function = functions[i]; + function_data = {}; + for (j = 0; j < functionicity.length; j += 1) { + function_data[functionicity[j]] = []; + } + for (name in the_function) { + if (Object.prototype.hasOwnProperty.call(the_function, name)) { + if (name.charAt(0) !== '(') { + kind = the_function[name]; + if (kind === 'unction' || kind === 'unparam') { + kind = 'unused'; + } + if (Array.isArray(function_data[kind])) { + function_data[kind].push(name); + if (kind === 'unused') { + unused.push({ + name: name, + line: the_function['(line)'], + 'function': the_function['(name)'] + }); + } else if (kind === 'undef') { + undef.push({ + name: name, + line: the_function['(line)'], + 'function': the_function['(name)'] + }); + } + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (function_data[functionicity[j]].length === 0) { + delete function_data[functionicity[j]]; + } + } + function_data.name = the_function['(name)']; + function_data.params = the_function['(params)']; + function_data.line = the_function['(line)']; + function_data.level = the_function['(level)']; + data.functions.push(function_data); + } + + if (unused.length > 0) { + data.unused = unused; + } + if (undef.length > 0) { + data.undefined = undef; + } + data.tokens = tokens; + return data; + }; + + itself.error_report = function (data) { + var evidence, i, output = [], snippets, warning; + if (data.errors) { + if (data.json) { + output.push('JSON: bad.
    '); + } + for (i = 0; i < data.errors.length; i += 1) { + warning = data.errors[i]; + if (warning) { + evidence = warning.evidence || ''; + output.push(''); + if (isFinite(warning.line)) { + output.push('
    line ' + + String(warning.line) + + ' character ' + String(warning.character) + + '
    '); + } + output.push(warning.reason.entityify() + '
    '); + if (evidence) { + output.push('
    ' + evidence.entityify() + '
    '); + } + } + } + } + if (data.unused || data.undefined) { + output.push('
    '); + if (data.undefined) { + output.push('
    undefined
    '); + snippets = []; + for (i = 0; i < data.undefined.length; i += 1) { + snippets[i] = '' + data.undefined[i].name + + ' 
    ' + + data.undefined[i]['function'] + ' ' + + String(data.undefined[i].line) + '
    '; + } + output.push(snippets.join(', ')); + output.push('
    '); + } + if (data.unused) { + output.push('
    unused
    '); + snippets = []; + for (i = 0; i < data.unused.length; i += 1) { + snippets[i] = '' + data.unused[i].name + ' 
    ' + + data.unused[i].function + ' ' + + String(data.unused[i].line) + '
    '; + } + output.push(snippets.join(', ')); + output.push('
    '); + } + output.push('
    '); + } + return output.join(''); + }; + + + itself.report = function (data) { + var dl, i, j, names, output = [], the_function; + + function detail(h, value) { + var comma_needed, singularity; + if (Array.isArray(value)) { + output.push("
    " + h + "
    "); + value.sort().forEach(function (item) { + if (item !== singularity) { + singularity = item; + output.push((comma_needed ? ', ' : '') + singularity); + comma_needed = true; + } + }); + output.push("
    "); + } else if (value) { + output.push("
    " + h + "
    ", value, "
    "); + } + } + + output.push('
    '); + if (data.urls) { + detail('url', data.urls); + dl = true; + } + if (data.globals) { + detail('global', data.globals); + dl = true; + } else if (xmode === 'style') { + output.push("
    CSS.
    "); + } else if (data.json) { + if (!data.errors) { + output.push("
    JSON: good.
    "); + } + } else { + output.push("
    No new global variables introduced.
    "); + } + if (dl) { + output.push("
    "); + } else { + output[0] = ''; + } + + if (data.functions) { + for (i = 0; i < data.functions.length; i += 1) { + the_function = data.functions[i]; + names = []; + if (the_function.params) { + for (j = 0; j < the_function.params.length; j += 1) { + names[j] = the_function.params[j].string; + } + } + output.push('
    line ' + String(the_function.line) + + '
    ' + the_function.name.entityify() + '(' + + names.join(', ') + ')'); + detail('undefined', the_function.undefined); + detail('unused', the_function.unused); + detail('closure', the_function.closure); + detail('variable', the_function.var); + detail('exception', the_function.exception); + detail('outer', the_function.outer); + detail('global', the_function.global); + detail('label', the_function.label); + output.push('
    '); + } + } + return output.join(''); + }; + + itself.properties_report = function (property) { + if (!property) { + return ''; + } + var i, + key, + keys = Object.keys(property).sort(), + length, + mem = ' ', + name, + not_first = false, + output = ['/*properties']; + for (i = 0; i < keys.length; i += 1) { + key = keys[i]; + if (property[key] > 0) { + if (not_first) { + mem += ', '; + } + name = ix.test(key) + ? key + : '\'' + key.replace(nx, sanitize) + '\''; + length += name.length + 2; + if (mem.length + name.length >= 80) { + output.push(mem); + mem = ' '; + } + mem += name; + not_first = true; + } + } + output.push(mem, '*/\n'); + return output.join('\n'); + }; + + itself.color = function (data) { + var from, + i = 1, + level, + line, + result = [], + thru, + token = data.tokens[0]; + while (token && token.id !== '(end)') { + from = token.from; + line = token.line; + thru = token.thru; + level = token.function['(level)']; + do { + thru = token.thru; + token = data.tokens[i]; + i += 1; + } while (token && token.line === line && token.from - thru < 5 && + level === token.function['(level)']); + result.push({ + line: line, + level: level, + from: from, + thru: thru + }); + } + return result; + }; + + itself.jslint = itself; + + itself.edition = '2013-02-18'; + + return itself; +}()); \ No newline at end of file diff --git a/html/admin/test/lib/underscore.js b/html/admin/test/lib/underscore.js new file mode 100644 index 0000000000..4d83099fd9 --- /dev/null +++ b/html/admin/test/lib/underscore.js @@ -0,0 +1,1221 @@ +// Underscore.js 1.4.3 +// http://underscorejs.org +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.3'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + return _.map(obj, function(value) { + return (_.isFunction(method) ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // with specific `key:value` pairs. + _.where = function(obj, attrs) { + if (_.isEmpty(attrs)) return []; + return _.filter(obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + index : index, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Reusable constructor function for prototype setting. + var ctor = function(){}; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Binding with arguments is also known as `curry`. + // Delegates to **ECMAScript 5**'s native `Function.bind` if available. + // We check for `func.bind` first, to fail fast when `func` is undefined. + _.bind = function(func, context) { + var args, bound; + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length == 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + (0 | Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function(object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = '' + ++idCounter; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + var render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + +}).call(this); diff --git a/html/admin/test/runnerJSLint.html b/html/admin/test/runnerJSLint.html new file mode 100644 index 0000000000..0bc2069ad3 --- /dev/null +++ b/html/admin/test/runnerJSLint.html @@ -0,0 +1,86 @@ + + + + Jasmine Test JSLint + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/html/admin/test/specJSLint/jsLintSpec.js b/html/admin/test/specJSLint/jsLintSpec.js new file mode 100644 index 0000000000..6392b4abc3 --- /dev/null +++ b/html/admin/test/specJSLint/jsLintSpec.js @@ -0,0 +1,68 @@ +describe('JSLint', function () { + var options = {}, + lint = /^\/specJSLint|.jsLintSpec\.js$/; + + function get(path) { + path = path + "?" + new Date().getTime(); + + var xhr; + try { + xhr = new jasmine.XmlHttpRequest(); + xhr.open("GET", path, false); + xhr.send(null); + } catch (e) { + throw new Error("couldn't fetch " + path + ": " + e); + } + if (xhr.status < 200 || xhr.status > 299) { + throw new Error("Could not load '" + path + "'."); + } + + return xhr.responseText; + } + + describe('checking codeFiles', function() { + var files = /^(.*lib\/.*)|(.*Spec\.js)$/; + + _.each(document.getElementsByTagName('script'), function (element) { + var script = element.getAttribute('src'); + if (files.test(script)) { + return; + } + it(script, function () { + var self = this; + var source = get(script); + var result = JSLINT(source, options); + _.each(JSLINT.errors, function (error) { + self.addMatcherResult(new jasmine.ExpectationResult({ + passed: false, + message: "line " + error.line + ' - ' + error.reason + ' - ' + error.evidence + })); + }); + expect(true).toBe(true); // force spec to show up if there are no errors + }); + }); + }); + + describe('checking specFiles', function() { + var files = /^\/spec*|.*Spec\.js$/; + + _.each(document.getElementsByTagName('script'), function (element) { + var script = element.getAttribute('src'); + if (!files.test(script) || lint.test(script)) { + return; + } + it(script, function () { + var self = this; + var source = get(script); + var result = JSLINT(source, options); + _.each(JSLINT.errors, function (error) { + self.addMatcherResult(new jasmine.ExpectationResult({ + passed: false, + message: "line " + error.line + ' - ' + error.reason + ' - ' + error.evidence + })); + }); + expect(true).toBe(true); // force spec to show up if there are no errors + }); + }); + }); +}); \ No newline at end of file