From 2d7e0247d0a7db29e9591f5085c821e9cdf139f5 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Wed, 24 Oct 2012 16:56:39 -0700 Subject: [PATCH 001/931] first go at client side tests that require a server. --- client/scripts/js-tests/README.md | 5 + .../js-tests/navHIstory/navHistoryTests.js | 201 ++++++++++++++++++ client/scripts/js-tests/runner.js | 99 +++++++++ client/scripts/js-tests/runtests.sh | 11 + .../js-tests/scriptedClientServerTests.html | 94 ++++++++ ...entTests.html => scriptedClientTests.html} | 21 +- client/scripts/scripted/utils/navHistory.js | 25 ++- client/scripts/setup.js | 2 +- play-area/.scripted | 0 .../node-static/lib/node-static.js | 22 +- server/router.js | 152 +++++++------ 11 files changed, 543 insertions(+), 89 deletions(-) create mode 100644 client/scripts/js-tests/README.md create mode 100644 client/scripts/js-tests/navHIstory/navHistoryTests.js create mode 100644 client/scripts/js-tests/runner.js create mode 100755 client/scripts/js-tests/runtests.sh create mode 100644 client/scripts/js-tests/scriptedClientServerTests.html rename client/scripts/js-tests/{allScriptedClientTests.html => scriptedClientTests.html} (72%) create mode 100644 play-area/.scripted diff --git a/client/scripts/js-tests/README.md b/client/scripts/js-tests/README.md new file mode 100644 index 00000000..cd0ebd9b --- /dev/null +++ b/client/scripts/js-tests/README.md @@ -0,0 +1,5 @@ +To launch the tests, run the runtests.sh script. You must have phantomjs on your path to launch. + +runner.js is a qunit extension to run qunit tests inside of phantomjs and is made available under the MIT license. The version used is the following commit from github: + +https://github.com/jquery/qunit/commit/9242bf7b7a743652702914adb770150a71492d6c diff --git a/client/scripts/js-tests/navHIstory/navHistoryTests.js b/client/scripts/js-tests/navHIstory/navHistoryTests.js new file mode 100644 index 00000000..4ae4f484 --- /dev/null +++ b/client/scripts/js-tests/navHIstory/navHistoryTests.js @@ -0,0 +1,201 @@ +/******************************************************************************* + * @license + * Copyright (c) 2012 VMware, Inc. All Rights Reserved. + * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE + * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE + * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT. + * You can obtain a current copy of the Eclipse Public License from + * http://www.opensource.org/licenses/eclipse-1.0.php + * + * Contributors: + * Andrew Eisenberg (VMware) - initial API and implementation + ******************************************************************************/ + +// Tests for navHistory.js, navigation and history +/*jslint browser:true */ +/*global $ define module localStorage window console */ + +define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], function(assert, mNavHistory) { + var xhrobj = new XMLHttpRequest(); + xhrobj.open("GET", '/test-api/server-root', false); + xhrobj.send(); // synchronous xhr request + var testResourcesRoot = xhrobj.responseText; + var testResourcesRootNoSlash; + if (testResourcesRoot.charAt(testResourcesRoot.length-1) !== '/') { + testResourcesRootNoSlash = testResourcesRoot; + testResourcesRoot += '/'; + } else { + testResourcesRootNoSlash = testResourcesRoot.substring(0, testResourcesRoot.length-1); + } + + function getFileContents(fname, callback) { + var xhrobj = new XMLHttpRequest(); + var url = 'http://localhost:7261/get?file=' + testResourcesRoot + fname; + xhrobj.open("GET",url,true); + xhrobj.onreadystatechange= function() { + if (xhrobj.readyState === 4) { + callback(xhrobj.responseText); + } + }; + xhrobj.send(); + } + + + function setup() { + window.fsroot = testResourcesRootNoSlash; + localStorage.removeItem("scriptedHistory"); + window.editor = mNavHistory.loadEditor(testResourcesRoot + "foo.js"); + mNavHistory.initializeBreadcrumbs(testResourcesRoot + "foo.js"); + } + + module('File loader tests'); + + var tests = {}; + tests.asyncTestGetContents = function() { + setup(); + setTimeout(function() { + assert.ok(window.editor); + + getFileContents("foo.js", + function(contents) { + assert.equal(window.editor.getText(), contents); + + getFileContents("bar.js", function(contents) { + window.editor = mNavHistory.loadEditor(testResourcesRoot + "bar.js"); + assert.equal(window.editor.getText(), contents); + assert.start(); + }); + }); + }, 500); + }; + tests.asyncTestToggleSidePanel = function() { + setup(); + assert.ok(!window.subeditors[0]); + $('#side_panel').css('display', 'none'); + mNavHistory.toggleSidePanel(); + + setTimeout(function() { + assert.ok(window.subeditors[0]); + assert.ok(window.editor.getText(), window.subeditors[0].getText()); + mNavHistory.toggleSidePanel(); + setTimeout(function() { + assert.ok(!window.subeditors[0]); + assert.start(); + }, 500); + }, 500); + }; + + tests.testBreadcrumb = function() { + setup(); + mNavHistory.initializeBreadcrumbs(testResourcesRoot + "bar.js"); + var breadcrumbs = $('#breadcrumb'); + assert.equal(breadcrumbs.children().length, 3); + assert.equal(breadcrumbs.children()[0], $('#historycrumb')[0]); + assert.equal(breadcrumbs.children()[1].innerHTML, "" + testResourcesRootNoSlash + ""); + assert.equal(breadcrumbs.children()[2].innerHTML, "bar.js"); + }; + + tests.testEmptyHistorycrumb = function() { + setup(); + mNavHistory.initializeBreadcrumbs(testResourcesRoot + "bar.js"); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + }; + + tests.testHistorycrumb1 = function() { + setup(); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + + // already on foo.js, navigate to itself + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js" }); + historyMenu = $("#history_menu"); + + assert.equal(historyMenu.children().length, 1); + assert.equal(historyMenu.children()[0].children[0].innerHTML, "foo.js"); + assert.equal(historyMenu.children()[0].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "foo.js" + "#0,0"); + }; + + tests.testHistorycrumb1a = function() { + setup(); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + + // navigate to new location. remember foo + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + historyMenu = $("#history_menu"); + + assert.equal(historyMenu.children().length, 1); + assert.equal(historyMenu.children()[0].children[0].innerHTML, "foo.js"); + assert.equal(historyMenu.children()[0].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "foo.js" + "#0,0"); + }; + + tests.testHistorycrumb2 = function() { + setup(); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "baz.js" }); + historyMenu = $("#history_menu"); + + assert.equal(historyMenu.children().length, 2); + assert.equal(historyMenu.children()[0].children[0].innerHTML, "bar.js"); + assert.equal(historyMenu.children()[0].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#0,0"); + assert.equal(historyMenu.children()[1].children[0].innerHTML, "foo.js"); + assert.equal(historyMenu.children()[1].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "foo.js" + "#0,0"); + }; + + tests.testHistorycrumb3 = function() { + setup(); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + window.editor.setSelection(10, 20); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + window.editor.setSelection(15, 25); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "baz.js" }); + historyMenu = $("#history_menu"); + + assert.equal(historyMenu.children().length, 2); + assert.equal(historyMenu.children()[0].children[0].innerHTML, "bar.js"); + assert.equal(historyMenu.children()[0].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#15,25"); + assert.equal(historyMenu.children()[1].children[0].innerHTML, "foo.js"); + assert.equal(historyMenu.children()[1].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "foo.js" + "#10,20"); + }; + + tests.testHistorycrumb4 = function() { + setup(); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + window.editor.setSelection(10, 20); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + window.editor.setSelection(15, 25); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "baz.js" }); + window.editor.setSelection(5, 10); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js" }); + window.editor.setSelection(6, 7); + + // this one is not stored in history yet + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js" }); + window.editor.setSelection(6, 8); + historyMenu = $("#history_menu"); + + assert.equal(historyMenu.children().length, 3); + assert.equal(historyMenu.children()[0].children[0].innerHTML, "foo.js"); + assert.equal(historyMenu.children()[0].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "foo.js" + "#6,7"); + assert.equal(historyMenu.children()[1].children[0].innerHTML, "baz.js"); + assert.equal(historyMenu.children()[1].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "baz.js" + "#5,10"); + assert.equal(historyMenu.children()[2].children[0].innerHTML, "bar.js"); + assert.equal(historyMenu.children()[2].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#15,25"); + }; + + return tests; +}); \ No newline at end of file diff --git a/client/scripts/js-tests/runner.js b/client/scripts/js-tests/runner.js new file mode 100644 index 00000000..aca50681 --- /dev/null +++ b/client/scripts/js-tests/runner.js @@ -0,0 +1,99 @@ +/* + * Qt+WebKit powered headless test runner using Phantomjs + * + * Phantomjs installation: http://code.google.com/p/phantomjs/wiki/BuildInstructions + * + * Run with: + * phantomjs runner.js [url-of-your-qunit-testsuite] + * + * E.g. + * phantomjs runner.js http://localhost/qunit/test + */ + +/*jshint latedef:false */ +/*global phantom:true require:true console:true */ +var url = phantom.args[0], + page = require('webpage').create(); + +// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") +page.onConsoleMessage = function(msg) { + console.log(msg); +}; + +page.onInitialized = function() { + page.evaluate(addLogging); +}; +page.open(url, function(status){ + if (status !== "success") { + console.log("Unable to access network: " + status); + phantom.exit(1); + } else { + // page.evaluate(addLogging); + var interval = setInterval(function() { + if (finished()) { + clearInterval(interval); + onfinishedTests(); + } + }, 500); + } +}); + +function finished() { + return page.evaluate(function(){ + return !!window.qunitDone; + }); +} + +function onfinishedTests() { + var output = page.evaluate(function() { + return JSON.stringify(window.qunitDone); + }); + phantom.exit(JSON.parse(output).failed > 0 ? 1 : 0); +} + +function addLogging() { + window.document.addEventListener( "DOMContentLoaded", function() { + var current_test_assertions = []; + + QUnit.testDone(function(result) { + var i, + name = result.module + ': ' + result.name; + + if (result.failed) { + console.log('Assertion Failed: ' + name); + + for (i = 0; i < current_test_assertions.length; i++) { + console.log(' ' + current_test_assertions[i]); + } + } + + current_test_assertions = []; + }); + + QUnit.log(function(details) { + var response; + + if (details.result) { + return; + } + + response = details.message || ''; + + if (typeof details.expected !== 'undefined') { + if (response) { + response += ', '; + } + + response += 'expected: ' + details.expected + ', but was: ' + details.actual; + } + + current_test_assertions.push('Failed assertion: ' + response); + }); + + QUnit.done(function(result){ + console.log('Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.'); + window.qunitDone = result; + }); + }, false ); +} + diff --git a/client/scripts/js-tests/runtests.sh b/client/scripts/js-tests/runtests.sh new file mode 100755 index 00000000..245f78eb --- /dev/null +++ b/client/scripts/js-tests/runtests.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +##################################################################### +## A launcher for the scripted test suite ## +## ## +## Must have phantomjs and scripted on your classpath ## +##################################################################### + +scr +phantomjs runner.js http://localhost:7261/scripts/js-tests/allScriptedClientTests.html +killserver diff --git a/client/scripts/js-tests/scriptedClientServerTests.html b/client/scripts/js-tests/scriptedClientServerTests.html new file mode 100644 index 00000000..1ad526a8 --- /dev/null +++ b/client/scripts/js-tests/scriptedClientServerTests.html @@ -0,0 +1,94 @@ + + + + + + Inferencing tests + + + + + + +
+ +
+ + + + +
+
+
+
+ +
+
+ +
+
    +
+
+
+ + + +
+ + \ No newline at end of file diff --git a/client/scripts/js-tests/allScriptedClientTests.html b/client/scripts/js-tests/scriptedClientTests.html similarity index 72% rename from client/scripts/js-tests/allScriptedClientTests.html rename to client/scripts/js-tests/scriptedClientTests.html index 67f075d0..2bff60d5 100644 --- a/client/scripts/js-tests/allScriptedClientTests.html +++ b/client/scripts/js-tests/scriptedClientTests.html @@ -1,22 +1,32 @@ + Inferencing tests - - -
+ + +
\ No newline at end of file diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index f7e501ac..498a41da 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -122,10 +122,14 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }; var storeBrowserState = function(histItem, doReplace) { - if (doReplace) { - window.history.replaceState(histItem, histItem.filename, histItem.url); - } else { - window.history.pushState(histItem, histItem.filename, histItem.url); + try { + if (doReplace) { + window.history.replaceState(histItem, histItem.filename, histItem.url); + } else { + window.history.pushState(histItem, histItem.filename, histItem.url); + } + } catch (e) { + console.log(e); } }; @@ -158,7 +162,8 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD -Open File */ var navigationEventHandler = function(event) { - var filepath = event.altTarget ? $(event.altTarget).attr('href') : $(event.currentTarget).attr('href') ; + var filepath = event.testTarget ? event.testTarget : ( + event.altTarget ? $(event.altTarget).attr('href') : $(event.currentTarget).attr('href')); var query_index = filepath.indexOf('?'); if (query_index !== -1) { filepath = filepath.substring(query_index+1, filepath.length); @@ -316,10 +321,10 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD var initializeHistoryMenu = function() { var historyCrumb = $('#historycrumb'); if (!historyCrumb.html()) { - historyCrumb = $('
  • '); + historyCrumb = $('
  • '); $('#breadcrumb').append(historyCrumb); } - var historyMenu = $(''); + var historyMenu = $(''); historyMenu.css('left', historyCrumb.position().left); historyMenu.css('top', $('header').height() + $('#breadcrumb').height()); $('#main').append(historyMenu); @@ -581,6 +586,12 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD * This handles initial page load */ var loadEditor = function(filepath, domNode, type) { + if (!type) { + type = "main"; + } + if (!domNode) { + domNode = $('#editor')[0]; + } $(domNode).show(); $('body').off('keydown'); var editor = mEditor.makeEditor(domNode, filepath, type); diff --git a/client/scripts/setup.js b/client/scripts/setup.js index 6cf68399..dfad4071 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -206,7 +206,7 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", }); var xhrobj = new XMLHttpRequest(); - var url = 'resources/shortcut.json'; + var url = '/resources/shortcut.json'; xhrobj.open("GET", url, false); // TODO naughty? synchronous xhr xhrobj.send(); var names = JSON.parse(xhrobj.responseText).names; diff --git a/play-area/.scripted b/play-area/.scripted new file mode 100644 index 00000000..e69de29b diff --git a/server/node_modules/node-static/lib/node-static.js b/server/node_modules/node-static/lib/node-static.js index 93992576..d1d39d6a 100644 --- a/server/node_modules/node-static/lib/node-static.js +++ b/server/node_modules/node-static/lib/node-static.js @@ -14,10 +14,10 @@ var util = require('./node-static/util'); var serverInfo = 'node-static/' + this.version.join('.'); // In-memory file store -this.store = {}; -this.indexStore = {}; +exports.store = {}; +exports.indexStore = {}; -this.Server = function (root, options) { +exports.Server = function (root, options) { if (root && (typeof(root) === 'object')) { options = root, root = null } this.root = path.resolve(root || '.'); @@ -46,7 +46,7 @@ this.Server = function (root, options) { } }; -this.Server.prototype.serveDir = function (pathname, req, res, finish) { +exports.Server.prototype.serveDir = function (pathname, req, res, finish) { var htmlIndex = path.join(pathname, 'index.html'), that = this; @@ -73,7 +73,7 @@ this.Server.prototype.serveDir = function (pathname, req, res, finish) { }); } }; -this.Server.prototype.serveFile = function (pathname, status, headers, req, res) { +exports.Server.prototype.serveFile = function (pathname, status, headers, req, res) { var that = this; var promise = new(events.EventEmitter); @@ -89,7 +89,7 @@ this.Server.prototype.serveFile = function (pathname, status, headers, req, res) }); return promise; }; -this.Server.prototype.finish = function (status, headers, req, res, promise, callback) { +exports.Server.prototype.finish = function (status, headers, req, res, promise, callback) { var result = { status: status, headers: headers, @@ -120,7 +120,7 @@ this.Server.prototype.finish = function (status, headers, req, res, promise, cal } }; -this.Server.prototype.servePath = function (pathname, status, headers, req, res, finish) { +exports.Server.prototype.servePath = function (pathname, status, headers, req, res, finish) { var that = this, promise = new(events.EventEmitter); @@ -152,10 +152,10 @@ this.Server.prototype.servePath = function (pathname, status, headers, req, res, } return promise; }; -this.Server.prototype.resolve = function (pathname) { +exports.Server.prototype.resolve = function (pathname) { return path.resolve(path.join(this.root, pathname)); }; -this.Server.prototype.serve = function (req, res, callback) { +exports.Server.prototype.serve = function (req, res, callback) { var that = this, promise = new(events.EventEmitter); @@ -175,7 +175,7 @@ this.Server.prototype.serve = function (req, res, callback) { if (! callback) { return promise } }; -this.Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) { +exports.Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) { var mtime = Date.parse(stat.mtime), key = pathname || files[0], headers = {}; @@ -224,7 +224,7 @@ this.Server.prototype.respond = function (pathname, status, _headers, files, sta } }; -this.Server.prototype.stream = function (pathname, files, buffer, res, callback) { +exports.Server.prototype.stream = function (pathname, files, buffer, res, callback) { (function streamFile(files, offset) { var file = files.shift(); diff --git a/server/router.js b/server/router.js index 3509deb5..af6abd54 100644 --- a/server/router.js +++ b/server/router.js @@ -11,87 +11,107 @@ * Andrew Clement - initial API and implementation * Kris De Volder - additional 'servlets' hookup ******************************************************************************/ -/*global require exports console*/ -var static = require('node-static'); +/*global require exports console process __dirname*/ +var stat = require('node-static'); var fs = require('fs'); var util = require('util'); +var path = require('path'); -if (process.platform.indexOf('win32') !== -1){ - var webroot = __dirname + "\\..\\client"; -} else { - var webroot = '../client'; +if (process.platform.indexOf('win32') !== -1) { + var webroot = __dirname + "\\..\\client"; +} else { + var webroot = '../client'; } -var file = new(static.Server)(webroot, { - cache: 600 +var file = new stat.Server(webroot, { + cache: 600 }); var jsdependPath = '/jsdepend/'; -var jsdepend = new(static.Server)('../jsdepend', { - cache: 600 +var jsdepend = new(stat.Server)('../jsdepend', { + cache: 600 }); function route(handle, pathname, response, request) { - //console.log("About to route a request for " + request.url); + //console.log("About to route a request for " + request.url); - // Check if there is a custom handler - if not then serve it as static content - if (typeof handle(pathname) === 'function') { - handle(pathname)(response, request); - } else { - if (pathname === '/') { - // for a '/' reference, give them the editor - file.serveFile('/editor.html', 200, {'content-type' : 'text/html'}, request, response); - } else if (pathname === '/helo') { - // this is the 'aliveness' test url - just returns 'helo' - response.writeHead(200,{"Content-Type":"text/plain"}); - response.write("helo"); - response.end(); - } else if (pathname.substring(0, jsdependPath.length) === jsdependPath) { - console.log('request for URL: '+request.url); - var segments = request.url.split('/'); - segments.splice(1,1); - request.url = segments.join('/'); - console.log('rewritten URL: '+request.url); - console.log('serving as static content from jsdepend'); - jsdepend.serve(request, response, function (err, result) { - if (err) { - if (err.errno === process.ENOENT) { - response.writeHead(404, {"Content-Type": "text/html"}); - response.write("404 Not found"); - response.end(); - } - console.error('Error serving %s - %s', request.url, err.message); - } else { - console.log('%s - %s', request.url, response.message); - } - }); - } else if (pathname.indexOf("/minimalEditor.html")==0) { - // likely a trailing bit on the pathname (the file that will be edited) but that piece - // will be handled by a separate XHR from the client when the editor is ready for content - file.serveFile('/minimaleditor.html', 200, {'content-type' : 'text/html'}, request, response); - } else if (pathname.indexOf("/fs_list")==0) { - // looks like a mucked up request from the dojo filestore - // e.g. /fs_list//Users/aclement/gits/jseditor/002_nodeserver - handle('/fs_list')(response,request,pathname.substr(9)); - } else if (pathname === '/scripts/htmlparser.js') { - jsdepend.serveFile("/node_modules/htmlparser/lib/htmlparser.js", 200, {'content-type': 'text/javascript'}, request, response); + // Check if there is a custom handler - if not then serve it as static content + if (typeof handle(pathname) === 'function') { + handle(pathname)(response, request); } else { + if (pathname === '/') { + // for a '/' reference, give them the editor + file.serveFile('/editor.html', 200, { + 'content-type': 'text/html' + }, request, response); + } else if (pathname === '/helo') { + // this is the 'aliveness' test url - just returns 'helo' + response.writeHead(200, { + "Content-Type": "text/plain" + }); + response.write("helo"); + response.end(); + } else if (pathname.substring(0, jsdependPath.length) === jsdependPath) { + console.log('request for URL: ' + request.url); + var segments = request.url.split('/'); + segments.splice(1, 1); + request.url = segments.join('/'); + console.log('rewritten URL: ' + request.url); + console.log('serving as static content from jsdepend'); + jsdepend.serve(request, response, function(err, result) { + if (err) { + if (err.errno === process.ENOENT) { + response.writeHead(404, { + "Content-Type": "text/html" + }); + response.write("404 Not found"); + response.end(); + } + console.error('Error serving %s - %s', request.url, err.message); + } else { + console.log('%s - %s', request.url, response.message); + } + }); + } else if (pathname.indexOf("/minimalEditor.html") === 0) { + // likely a trailing bit on the pathname (the file that will be edited) but that piece + // will be handled by a separate XHR from the client when the editor is ready for content + file.serveFile('/minimaleditor.html', 200, { + 'content-type': 'text/html' + }, request, response); + } else if (pathname.indexOf("/fs_list") === 0) { + // looks like a mucked up request from the dojo filestore + // e.g. /fs_list//Users/aclement/gits/jseditor/002_nodeserver + handle('/fs_list')(response, request, pathname.substr(9)); + } else if (pathname === '/scripts/htmlparser.js') { + jsdepend.serveFile("/node_modules/htmlparser/lib/htmlparser.js", 200, { + 'content-type': 'text/javascript' + }, request, response); + } else if (pathname === '/test-api/server-root') { + // This is used during testing to find the root of the play-arĂ…ea, where test resources are stored + response.writeHead(200, { + "Content-Type": "text/plain" + }); + response.write(path.resolve('../play-area/')); + response.end(); + } else { - //console.log("No request handler found for " + pathname+" so serving static content"); - file.serve(request, response, function(err, result) { - if (err) { - console.error('Error serving %s - %s', request.url, err.message); - if (err.errno === process.ENOENT) { - response.writeHead(404, {"Content-Type": "text/html"}); - response.write("404 Not found"); - response.end(); + //console.log("No request handler found for " + pathname+" so serving static content"); + file.serve(request, response, function(err, result) { + if (err) { + console.error('Error serving %s - %s', request.url, err.message); + if (err.errno === process.ENOENT) { + response.writeHead(404, { + "Content-Type": "text/html" + }); + response.write("404 Not found"); + response.end(); + } + } else { + console.log('%s - %s', request.url, response.message); + } + }); } - } else { - console.log('%s - %s', request.url, response.message); - } - }); } - } } -exports.route = route; +exports.route = route; \ No newline at end of file From 66b9cfb70b055a8ec2d9c74604718ff8461fedff Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Wed, 24 Oct 2012 17:05:46 -0700 Subject: [PATCH 002/931] On opening a file in non-existent dir, create the dir Rather than error, when you open scripted against a missing file in a missing directory we will create the directories. Let's see how this goes - it is a bit unusual but if you aren't making typos it could be convenient. Issue: SCRIPTED-209 --- .../scripts/plugins/esprima/indexerService.js | 2 +- .../scripts/scripted/editor/scriptedEditor.js | 7 +- client/scripts/scripted/utils/navHistory.js | 43 +++++------ client/scripts/setup.js | 19 +---- server/requestHandlers.js | 72 +++++++++++++++++++ server/scripted.js | 1 + 6 files changed, 104 insertions(+), 40 deletions(-) diff --git a/client/scripts/plugins/esprima/indexerService.js b/client/scripts/plugins/esprima/indexerService.js index 7607749d..ae0fd49c 100644 --- a/client/scripts/plugins/esprima/indexerService.js +++ b/client/scripts/plugins/esprima/indexerService.js @@ -107,7 +107,7 @@ define(["plugins/esprima/esprimaJsContentAssist", "servlets/jsdepend-client"], f jsdepend.getContents(file, function (contents) { var oldFile = indexer.getTargetFile(); indexer.setTargetFile(file); - var esprimaContentAssistant = new mEsprimaContentAssist.EsprimaJavaScriptContentAssistProvider(indexer, indexer.jslint); + var esprimaContentAssistant = new mEsprimaContentAssist.EsprimaJavaScriptContentAssistProvider(indexer, indexer.jslintConfig); var structure = esprimaContentAssistant.computeSummary(contents, file); var textStructure = JSON.stringify(structure); var ts = generateTimeStamp(); diff --git a/client/scripts/scripted/editor/scriptedEditor.js b/client/scripts/scripted/editor/scriptedEditor.js index fa0d9b25..b6d7fe3e 100644 --- a/client/scripts/scripted/editor/scriptedEditor.js +++ b/client/scripts/scripted/editor/scriptedEditor.js @@ -17,7 +17,8 @@ /*jslint browser:true devel:true*/ define(["require", "orion/textview/textView", "orion/textview/keyBinding", "orion/editor/editor", "orion/editor/editorFeatures", "examples/textview/textStyler", -"orion/editor/textMateStyler", "plugins/esprima/esprimaJsContentAssist", "orion/editor/jsTemplateContentAssist", "orion/editor/contentAssist", "plugins/esprima/indexerService", "orion/editor/jslintdriver", +"orion/editor/textMateStyler", "plugins/esprima/esprimaJsContentAssist", "orion/editor/jsTemplateContentAssist", "orion/editor/contentAssist", +"plugins/esprima/indexerService", "orion/editor/jslintdriver", "orion/searchAndReplace/textSearcher", "orion/selection", "orion/commands", "orion/parameterCollectors", "orion/editor/htmlGrammar", "plugins/esprima/moduleVerifier", "orion/editor/jslintworker", "jsbeautify","orion/textview/textModel","orion/textview/projectionTextModel", "orion/editor/htmlContentAssist", "orion/editor/cssContentAssist", "scripted/exec/exec-keys", "scripted/exec/exec-after-save"], @@ -112,7 +113,7 @@ mHtmlContentAssist, mCssContentAssist) { } var indexer = new mIndexerService.Indexer(); - indexer.jslingConfig = window.scripted && window.scripted.config && window.scripted.config.jslint; + indexer.jslintConfig = window.scripted && window.scripted.config && window.scripted.config.jslint; var selection = new mSelection.Selection(); var commandService = new mCommands.CommandService({ @@ -469,7 +470,7 @@ mHtmlContentAssist, mCssContentAssist) { var xhrobj = new XMLHttpRequest(); try { - var url = 'http://localhost:7261/get?file=' + filePath; + var url = 'http://localhost:7261/get2?file=' + filePath; //console.log("Getting contents for " + url); xhrobj.open("GET", url, false); // synchronous xhr diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index 498a41da..af0964f6 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -357,40 +357,43 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD for (var i = 0, len = crumbs.length; i < len; i++) { newCrumbElem = $('
  • ' + crumbs[i] + '
  • '); - $('#breadcrumb').append(newCrumbElem); if (i + 1 === len) { constructedPath += crumbs[i]; - } - else { + } else { constructedPath += crumbs[i] + '/'; url = 'http://localhost:7261/fs_list/'+constructedPath.substring(0, constructedPath.length-1); xhrobj = new XMLHttpRequest(); xhrobj.open("GET",url,false); // TODO naughty? synchronous xhr xhrobj.send(); - var kids = JSON.parse(xhrobj.responseText).children; - if (kids) { + if (xhrobj.status !== 200) { + i=len; // terminate early - the rest of the directory structure looks like it does not exist + } else { + $('#breadcrumb').append(newCrumbElem); + var kids = JSON.parse(xhrobj.responseText).children; + if (kids) { - kids.sort(fileEntryCompare); + kids.sort(fileEntryCompare); - var newMenu = $(''); - for(var j = 0; j < kids.length; j++) { - if (kids[j].directory === false) { - if (kids[j].name.lastIndexOf('.',0)!==0) { - var href = basepath + kids[j].Location; - var newMenuItem = $('
  • '); - var newMenuAnchor = $(''+kids[j].name+''); - newMenuItem.append(newMenuAnchor); - newMenu.prepend(newMenuItem); + var newMenu = $(''); + for(var j = 0; j < kids.length; j++) { + if (kids[j].directory === false) { + if (kids[j].name.lastIndexOf('.',0)!==0) { + var href = basepath + kids[j].Location; + var newMenuItem = $('
  • '); + var newMenuAnchor = $(''+kids[j].name+''); + newMenuItem.append(newMenuAnchor); + newMenu.prepend(newMenuItem); - $(newMenuAnchor).click(navigationEventHandler); + $(newMenuAnchor).click(navigationEventHandler); + } } } + newMenu.css('left', newCrumbElem.position().left); + newMenu.css('min-width', newCrumbElem.outerWidth()); + newMenu.css('top', $('header').height() + $('#breadcrumb').height()); + $('#main').append(newMenu); } - newMenu.css('left', newCrumbElem.position().left); - newMenu.css('min-width', newCrumbElem.outerWidth()); - newMenu.css('top', $('header').height() + $('#breadcrumb').height()); - $('#main').append(newMenu); } } } diff --git a/client/scripts/setup.js b/client/scripts/setup.js index dfad4071..52790290 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -101,23 +101,9 @@ requirejs.config({ } }); -require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", "fileapi", "orion/textview/keyBinding", "orion/searchClient", "scripted/widgets/OpenResourceDialog", "jquery", "scripted/utils/navHistory", "servlets/jsdepend-client", "scripted/exec/exec-on-load"], +require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", "fileapi", "orion/textview/keyBinding", "orion/searchClient", + "scripted/widgets/OpenResourceDialog", "jquery", "scripted/utils/navHistory", "servlets/jsdepend-client", "scripted/exec/exec-on-load"], function(mEditor, mExplorerTable, mFileApi, mKeyBinding, mSearchClient, mOpenResourceDialog, mJquery, mNavHistory, mJsdepend) { - - /* needs dependency on "sockjs" - var sock = new SockJS("http://localhost:7261/echo"); - sock.onopen = function() { - scriptedLogger.info('open', "SETUP"); - sock.send("helo"); - }; - sock.onmessage = function(e) { - scriptedLogger.info('message', "SETUP"); - scriptedLogger.info(e.data, "SETUP"); - }; - sock.onclose = function() { - scriptedLogger.info('close', "SETUP"); - }; - */ if (!window.scripted) { window.scripted = {}; @@ -125,6 +111,7 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", var filepath = window.location.getPath(); + // TODO why is getConf on jsdepend? mJsdepend.getConf(filepath, function (dotScripted) { //console.log("fetched dot-scripted conf from server"); //console.log(JSON.stringify(dotScripted, null, " ")); diff --git a/server/requestHandlers.js b/server/requestHandlers.js index e280377a..e9dcb054 100644 --- a/server/requestHandlers.js +++ b/server/requestHandlers.js @@ -86,6 +86,77 @@ function get(response, request) { }); } +/** + * Similar to get() but for files that do not exist this will attempt to create any + * missing directories on the path to the file. Let's see how it goes using this. + * TODO use flags on the get() to indicate we want to 'build the missing dirs' then remove this dup + */ +function get2(response, request) { + var file = url.parse(request.url,true).query.file; + //console.log("Processing get request for "+file); + fs.readFile(file, function(err,data){ + if(err) { + if (err.errno === 28 /*EISDIR*/) { + // is a directory + response.writeHead(500, { "Content-Type": "text/plain"}); + response.write("File is a directory"); + response.end(); + } else if (err.errno === 34 /*ENOENT*/) { + // File not found + // Let's create the folders leading to it - the file itself will + // be created when the user saves. + var toBuild = []; + var lastSlash = file.lastIndexOf('/'); + while (lastSlash!==-1) { + var aDirectory = file.substring(0,lastSlash); + var directoryExists = fs.existsSync(aDirectory); + if (!directoryExists) { + toBuild.unshift(aDirectory); + lastSlash = aDirectory.lastIndexOf('/'); + } else { + lastSlash=-1; + } + } + if (toBuild.length!==0) { + for (var i=0;i Date: Thu, 25 Oct 2012 08:07:10 -0700 Subject: [PATCH 003/931] Surface the editor config options for tabbing The options editor.expandtab (boolean) and editor.tabsize (number) can now be set in .scripted. These control whether tabs are inserted as groups of spaces (if expandtab is true) and how many spaces to represent a tab with. Issue: SCRIPTED-158 --- client/scripts/scripted/editor/scriptedEditor.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/client/scripts/scripted/editor/scriptedEditor.js b/client/scripts/scripted/editor/scriptedEditor.js index b6d7fe3e..47b3e091 100644 --- a/client/scripts/scripted/editor/scriptedEditor.js +++ b/client/scripts/scripted/editor/scriptedEditor.js @@ -150,12 +150,13 @@ mHtmlContentAssist, mCssContentAssist) { /** * This function is called after successful save. */ - function afterSaveSuccess(filePath) { + function afterSaveSuccess(filePath) { editor.dispatchEvent({type: "afterSave", file: filePath}); } var textViewFactory = function() { - return new mTextView.TextView({ + + var options = { parent: domNode, // without this, the listeners aren't registered in quite the right order, meaning that the // one that shuffles annotations along when text is entered (annotations.js _onChanged) @@ -167,7 +168,14 @@ mHtmlContentAssist, mCssContentAssist) { // the style that indicates the 'current line'.1112412344443444 model: new mProjectionModel.ProjectionTextModel(new mTextModel.TextModel()), tabSize: 4 - }); + }; + if (window.scripted.config.editor && window.scripted.config.editor.expandtab) { + options.expandTab = window.scripted.config.editor.expandtab; + } + if (window.scripted.config.editor && window.scripted.config.editor.tabsize) { + options.tabSize = window.scripted.config.editor.tabsize; + } + return new mTextView.TextView(options); }; var contentAssistFactory = function(editor) { From c153a6971bc0cb62856316d10de225014575d933 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Thu, 25 Oct 2012 08:07:37 -0700 Subject: [PATCH 004/931] example config of editor tabbing options --- .scripted | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.scripted b/.scripted index c47be5fa..cf29dbec 100644 --- a/.scripted +++ b/.scripted @@ -87,8 +87,13 @@ { "ui": { "font": "Monaco" + }, + "editor": { + "expandtab": true, + "tabsize": 4 } } */ { + } From 1c69d28f379c70a121a3259d56625b8b51f4cb27 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 25 Oct 2012 09:27:44 -0700 Subject: [PATCH 005/931] Exec console ui, initial implementation --- client/css/main.css | 1 + client/editor.html | 3 +- client/scripts/scripted/exec/console.css | 33 +++++ client/scripts/scripted/exec/exec-console.js | 134 +++++++++++++++++++ client/scripts/scripted/exec/exec-shared.js | 21 +-- client/scripts/setup.js | 12 +- 6 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 client/scripts/scripted/exec/console.css create mode 100644 client/scripts/scripted/exec/exec-console.js diff --git a/client/css/main.css b/client/css/main.css index b79d76a8..389ed87b 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -1,4 +1,5 @@ @import "../scripts/orion/editor/editor.css"; +@import "../scripts/scripted/exec/console.css"; ul{ display: block; diff --git a/client/editor.html b/client/editor.html index b76c354c..d9238594 100644 --- a/client/editor.html +++ b/client/editor.html @@ -25,7 +25,8 @@
    -
    +
    +
    diff --git a/client/scripts/scripted/exec/console.css b/client/scripts/scripted/exec/console.css new file mode 100644 index 00000000..1e5d896f --- /dev/null +++ b/client/scripts/scripted/exec/console.css @@ -0,0 +1,33 @@ +#console_wrapper { + background-color: #FFFFFF; + overflow-x: auto; + overflow-y: scroll; + display: none; + padding:6px; + border: 1px solid grey; +} + +#console_wrapper > div { + background-color: background-color:rgba(0,0,0,0); + white-space: pre; + font-family: monospace; + font-size: 11px; + display: block; +} + +.command:before { + content: "> " +} + +#console_wrapper > .command { + font-weight:bold; + padding: 3px; + border: 1px solid grey; + margin-top: 3px; + margin-bottom: 3px; +} + +#console_wrapper > .error { + font-weight:bold; + color:red; +} diff --git a/client/scripts/scripted/exec/exec-console.js b/client/scripts/scripted/exec/exec-console.js new file mode 100644 index 00000000..4459a1dd --- /dev/null +++ b/client/scripts/scripted/exec/exec-console.js @@ -0,0 +1,134 @@ +/******************************************************************************* + * @license + * Copyright (c) 2012 VMware, Inc. All Rights Reserved. + * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE + * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE + * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT. + * You can obtain a current copy of the Eclipse Public License from + * http://www.opensource.org/licenses/eclipse-1.0.php + * + * Contributors: + * Kris De Volder - initial API and implementation + ******************************************************************************/ + +/////////////////////////////////////////////////////////////////////////////////// +// This module is supposed to provide a console-like api for displaying messages. +// as well as some basic operations to reveal/hide the console. + +define(["jquery", "jquery_ui"], function () { + + /** + * The id of the dom element to which we append console output. + */ + var CONSOLE_DISPLAY = "#console_wrapper"; + + var initialized = false; + + /** + * A helper method that creates a dom element to display a given msg. + * For now all we know how to render is plain text messages. + */ + function render(msg, cssClass) { + var elem = $('
    ', {text: msg}); + if (cssClass) { + elem.addClass(cssClass); + } + return elem; + } + + //////////////////// Public API /////////////////////////////////////////////////// + + /** + * This method should be called by the editor setup at a good time (i.e. when editor has + * mostly been setup. + */ + function initialize() { + if (!initialized) { + initialized = true; + $(CONSOLE_DISPLAY).resizable({ + handles: "n" + }); + $(CONSOLE_DISPLAY).resize(function (event, ui) { + var console_height = ui.size.height; + var breadcrumb_height = $('#breadcrumb').outerHeight(); + var editor_height = $('#main').height() - breadcrumb_height - console_height; + $(CONSOLE_DISPLAY).css('top', '0px'); //I think JQuery resizable is changing this from 0 but it messes things up. + $(CONSOLE_DISPLAY).height(console_height); + $("#editor").height(editor_height); + window.editor._textView._updatePage(); + }); + } + } + + function show() { + initialize(); + var c = $(CONSOLE_DISPLAY); + var e = $("#editor"); + if (c.css('display')==='none') { + //If the console is presently hidden... + var editor_height = e.height(); + var console_height = editor_height / 3; + editor_height = editor_height - console_height; + + var overhead = c.outerHeight() - c.height(); + + c.css('display', 'block'); + c.height(console_height-overhead); + e.height(editor_height); + window.editor._textView._updatePage(); + } + } + + function scrollToBottom() { + var c = $(CONSOLE_DISPLAY); + var scrollH = c.prop("scrollHeight"); + var H = c.height(); + if (H < scrollH) { + c.scrollTop(scrollH-H); + } + } + + /** + * Append a message to the console. + * An optional cssClass parameter allows tagging the + * element in the view with a specific cssClass. (so that it's + * look can be customized via css). + * + * @param String msg + * @param String cssClass + */ + function log(msg, cssClass) { + cssClass = cssClass || 'log'; + show(); + var e = render(msg, cssClass); + $(CONSOLE_DISPLAY).append(e); + scrollToBottom(); + } + + function hide() { + //TODO: this code is not really correct. +// var c = $(CONSOLE_DISPLAY); +// var e = $("#editor"); +// if (c.css('display')==='block') { +// //If the console is presently shown... +// var editor_height = e.height(); +// var console_height = c.outerHeight(); +// var padding = consoleHeig +// editor_height = editor_height - console_height; +// +// c.css('display', 'block'); +// c.height(console_height); +// e.height(editor_height); +// window.editor._textView._updatePage(); +// } + } + + return { + log: log, + error: function (msg) { + log(msg, 'error'); + } + }; + +}); + diff --git a/client/scripts/scripted/exec/exec-shared.js b/client/scripts/scripted/exec/exec-shared.js index cf858771..a7d6b337 100644 --- a/client/scripts/scripted/exec/exec-shared.js +++ b/client/scripts/scripted/exec/exec-shared.js @@ -21,9 +21,10 @@ // TODO: actually, exec-keys isn't using this yet. The code was merely copied // from it. ///////////////////////////// -define(['require', 'servlets/exec-client'], function (require) { +define(['require', 'servlets/exec-client', 'scripted/exec/exec-console'], function (require) { var exec = require('servlets/exec-client').exec; + var execConsole = require('scripted/exec/exec-console'); /** * Follow a 'trail' of properties starting at given object. @@ -57,14 +58,14 @@ define(['require', 'servlets/exec-client'], function (require) { } } - var execConsole = { - log : function (msg) { - console.log(render(msg)); - }, - error: function (msg) { - console.error(render(msg)); - } - }; +// var execConsole = { +// log : function (msg) { +// console.log(render(msg)); +// }, +// error: function (msg) { +// console.error(render(msg)); +// } +// }; function makeExecFunction(cmdSpec) { //Start by normalizing the cmdSpec so it always has 'object form' and provides @@ -79,7 +80,7 @@ define(['require', 'servlets/exec-client'], function (require) { return function(replaceParams) { var cmd = replaceParams(cmdSpec); - execConsole.log("exec: " + cmd.cmd); + execConsole.log(cmd.cmd, "command"); exec(cmd, function(error, stdout, stderr) { if (stdout) { diff --git a/client/scripts/setup.js b/client/scripts/setup.js index 6cf68399..49b87f19 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -126,8 +126,8 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", var filepath = window.location.getPath(); mJsdepend.getConf(filepath, function (dotScripted) { - //console.log("fetched dot-scripted conf from server"); - //console.log(JSON.stringify(dotScripted, null, " ")); + console.log("fetched dot-scripted conf from server"); + console.log(JSON.stringify(dotScripted, null, " ")); window.fsroot = dotScripted.fsroot; window.scripted.config = dotScripted; if (window.scripted.config && window.scripted.config.ui && window.scripted.config.ui.navigator===false) { @@ -333,6 +333,12 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", $(window).resize(function(e){ var main_height = $(window).height() - footer_height - header_height; + var console_height; + if ($('#console_wrapper').css('display')==='none') { + console_height = 0; + } else { + console_height = $('#console_wrapper').outerHeight() || 0; + } $('#main').height(main_height); $('#side_panel').height(main_height); @@ -346,7 +352,7 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", $('.subeditor_titlebar').height() ); - $('#editor').height($('#main').height() - breadcrumb_height); + $('#editor').height($('#main').height() - breadcrumb_height - console_height); var side_width = ($('#side_panel').css('display') === 'block') ? $('#side_panel').width() : 0; $('#editor').css('margin-right', side_width); From 64ba57f760432150bda3c5e762bd35166a2b2f0b Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Thu, 25 Oct 2012 09:48:17 -0700 Subject: [PATCH 006/931] refactor navHistory so that loadEditor is no longer a public function. Still expose the function, but for testing only. --- .../js-tests/navHIstory/navHistoryTests.js | 9 +++- .../js-tests/scriptedClientServerTests.html | 10 ----- client/scripts/scripted/utils/navHistory.js | 43 ++++++++++++------- client/scripts/setup.js | 24 +++++++---- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/client/scripts/js-tests/navHIstory/navHistoryTests.js b/client/scripts/js-tests/navHIstory/navHistoryTests.js index 4ae4f484..010319f9 100644 --- a/client/scripts/js-tests/navHIstory/navHistoryTests.js +++ b/client/scripts/js-tests/navHIstory/navHistoryTests.js @@ -44,7 +44,7 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio function setup() { window.fsroot = testResourcesRootNoSlash; localStorage.removeItem("scriptedHistory"); - window.editor = mNavHistory.loadEditor(testResourcesRoot + "foo.js"); + window.editor = mNavHistory._loadEditor(testResourcesRoot + "foo.js"); mNavHistory.initializeBreadcrumbs(testResourcesRoot + "foo.js"); } @@ -61,7 +61,7 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio assert.equal(window.editor.getText(), contents); getFileContents("bar.js", function(contents) { - window.editor = mNavHistory.loadEditor(testResourcesRoot + "bar.js"); + window.editor = mNavHistory._loadEditor(testResourcesRoot + "bar.js"); assert.equal(window.editor.getText(), contents); assert.start(); }); @@ -197,5 +197,10 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio assert.equal(historyMenu.children()[2].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#15,25"); }; + // still to test + + // raw history object + // subeditor and state + return tests; }); \ No newline at end of file diff --git a/client/scripts/js-tests/scriptedClientServerTests.html b/client/scripts/js-tests/scriptedClientServerTests.html index 1ad526a8..7ec6cd97 100644 --- a/client/scripts/js-tests/scriptedClientServerTests.html +++ b/client/scripts/js-tests/scriptedClientServerTests.html @@ -29,16 +29,6 @@ }); window.onload = function() { - // TODO move this to a test-utils class - var xhrobj = new XMLHttpRequest(); - xhrobj.open("GET", '/test-api/server-root', false); - xhrobj.send(); // synchronous xhr request - console.log("Found: " + xhrobj.responseText); - var initialFile = "?" + xhrobj.responseText + "/foo.js"; - if (location.search !== initialFile) { - location.search = initialFile; - } - require(["js-tests/navHistory/navHistoryTests"], function(navHistoryTests) { function testShim(tests) { diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index af0964f6..081c6c0b 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -654,18 +654,23 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD * @return {boolean} true if navigation occurred successfully and false otherwise. */ navigate = function(filepath, range, target, doSaveState) { - var histItem = generateHistoryItem(window.editor); - storeScriptedHistory(histItem); - if (doSaveState) { - storeBrowserState(histItem, true); - } - if (window.subeditors[0]) { - var subHistItem = generateHistoryItem(window.subeditors[0]); - storeScriptedHistory(subHistItem); + // check if the editor has been created yet, or if + // window.editor is a dom node + var histItem; + var hasEditor = window.editor.getText; + if (hasEditor) { + histItem = generateHistoryItem(window.editor); + storeScriptedHistory(histItem); + if (doSaveState) { + storeBrowserState(histItem, true); + } + if (window.subeditors[0]) { + var subHistItem = generateHistoryItem(window.subeditors[0]); + storeScriptedHistory(subHistItem); + } } - if (target === EDITOR_TARGET.sub) { - if (!confirmNavigation(window.subeditors[0])) { + if (hasEditor && !confirmNavigation(window.subeditors[0])) { return false; } open_side(window.editor); @@ -682,12 +687,12 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } initializeHistoryMenu(); window.subeditors[0].getTextView().focus(); - } else if (target === "main") { - if (!confirmNavigation(window.editor)) { + } else if (target === EDITOR_TARGET.main) { + if (hasEditor && !confirmNavigation(window.editor)) { return false; } $('#editor').css('display','block'); - window.editor = loadEditor(filepath, $('#editor')[0], 'main'); + window.editor = loadEditor(filepath, $('#editor')[0], EDITOR_TARGET.main); if (range) { window.editor.getTextView().setSelection(range[0], range[1], false); scrollDefinition(window.editor); @@ -695,7 +700,10 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD // explicit check for false since navigator might be 'undefined' at this point if (window.scripted.navigator !== false) { - explorer.highlight(filepath); + // if model not yet available, highlighting is handled elsewhere. + if (explorer.model) { + explorer.highlight(filepath); + } } initializeBreadcrumbs(filepath); window.editor.getTextView().focus(); @@ -714,11 +722,14 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }; return { + // loadEditor is a private function and only exposed for testing purposes + _loadEditor: loadEditor, + initializeBreadcrumbs: initializeBreadcrumbs, navigationEventHandler: navigationEventHandler, - loadEditor: loadEditor, highlightSelection: highlightSelection, popstateHandler: popstateHandler, - toggleSidePanel: toggleSidePanel + toggleSidePanel: toggleSidePanel, + navigate: navigate }; }); diff --git a/client/scripts/setup.js b/client/scripts/setup.js index 52790290..d5289b4c 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -133,13 +133,21 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", }); window.explorer = explorer; - window.editor = mNavHistory.loadEditor( filepath, ($('#editor')[0]), 'main' ); - if (location.hash.length > 1) { - mNavHistory.highlightSelection(window.editor); +// window.editor = mNavHistory.loadEditor( filepath, ($('#editor')[0]), 'main' ); +// if (location.hash.length > 1) { +// mNavHistory.highlightSelection(window.editor); +// } +// +// mNavHistory.initializeBreadcrumbs(filepath); + var range; + try { + range = JSON.parse('[' + location.hash.substring(1) +']'); + if (!range.length || range.length < 2) { + range = null; + } + } catch (e) { } - mNavHistory.initializeBreadcrumbs(filepath); - if (window.scripted.navigator === undefined || window.scripted.navigator === true) { explorer.loadResourceList(window.fsroot/*pageParams.resource*/, false, function() { // mGlobalCommands.setPageTarget(explorer.treeRoot, serviceRegistry, commandService, null, /* favorites target */explorer.treeRoot); @@ -151,6 +159,8 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", $('#editor').css("left","0px"); $('#explorer-tree').remove(); } + window.subeditors = []; + mNavHistory.navigate(filepath, range, "main", false); require(['jquery_ui'], function(mJqueryUI){ /*Resizable navigator*/ @@ -180,7 +190,7 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", $('#side_panel').css('width', side_percent + "%"); window.editor._textView._updatePage(); - for (var i in window.subeditors){ + for (var i = 0; i < window.subeditors.length; i++){ window.subeditors[i].getTextView().update(); } }); @@ -369,8 +379,6 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", }, 1); }); - window.subeditors = []; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // HACK sections // Here we do a few things that are not pretty, but is the only way to get things working in all the browsers From 8931e78f432a605e43a7bdb2891b1c361e5703d4 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 25 Oct 2012 10:49:30 -0700 Subject: [PATCH 007/931] Add a jslint global thingy to our .scripted file. --- .scripted | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.scripted b/.scripted index cf29dbec..ce8f1ec6 100644 --- a/.scripted +++ b/.scripted @@ -95,5 +95,7 @@ } */ { - + "jslint" : { + "global" : ["define", "require", "$"] + } } From a783842da44e263c6690b7d4a8ccc60c852c67ec Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Thu, 25 Oct 2012 11:09:59 -0700 Subject: [PATCH 008/931] Ensure editor/formatter indentation options agree If the editor is configured, the formatter will be configured the same, and vice versa, where possible. Some formatter options cannot be set on the editor (e.g. the formatter supports any indent char, the editor supports tabs or spaces) Issue: SCRIPTED-217 --- .../scripts/scripted/editor/scriptedEditor.js | 5 ++ client/scripts/setup.js | 77 ++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/client/scripts/scripted/editor/scriptedEditor.js b/client/scripts/scripted/editor/scriptedEditor.js index 47b3e091..96f01c74 100644 --- a/client/scripts/scripted/editor/scriptedEditor.js +++ b/client/scripts/scripted/editor/scriptedEditor.js @@ -238,6 +238,11 @@ mHtmlContentAssist, mCssContentAssist) { var end = selection.end; var selectionEmpty = start === end; var options = window.scripted.config && window.scripted.config.formatter && window.scripted.config.formatter.js ? window.scripted.config.formatter.js : {}; + // If nothing specified let's at least make the options match the editor defaults + if (!options.indent_size && !options.indent_char) { + options.indent_size = 1; + options.indent_char = "\t"; + } var toFormat, formatted; if (!selectionEmpty) { var checkedFormatSelection = checkFormatSelection(editor, start, end); diff --git a/client/scripts/setup.js b/client/scripts/setup.js index fce9a46e..661fe7b7 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -103,6 +103,7 @@ requirejs.config({ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", "fileapi", "orion/textview/keyBinding", "orion/searchClient", "scripted/widgets/OpenResourceDialog", "jquery", "scripted/utils/navHistory", "servlets/jsdepend-client", "scripted/exec/exec-on-load"], + function(mEditor, mExplorerTable, mFileApi, mKeyBinding, mSearchClient, mOpenResourceDialog, mJquery, mNavHistory, mJsdepend) { if (!window.scripted) { @@ -111,6 +112,78 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", var filepath = window.location.getPath(); + /** + * This function will perform checks on the configuration and where appropriate ensure options are consistent. + * Currently, it: + * 1) ensures if formatter indentation is configured, it sets editor indentation options, and vice versa + */ + var processConfiguration = function() { + + // 1. Ensuring consistency of options across formatter and editor configuration + // formatter configuration options: + // formatter.js.indent_size (number) + // formatter.js.indent_char (string) + // editor configuration options: + // editor.expandtab (boolean) + // editor.tabsize (number) + // rule: if possible (compatible), copy one config to the other + var editor_expandtab_set = window.scripted.config.editor && window.scripted.config.editor.expandtab!==null; + var editor_tabsize_set = window.scripted.config.editor && window.scripted.config.editor.tabsize!==null; + var formatter_js_indent_size_set = window.scripted.config.formatter && window.scripted.config.formatter.js && + window.scripted.config.formatter.js.indent_size!==null; + var formatter_js_indent_char_set = window.scripted.config.formatter && window.scripted.config.formatter.js && + window.scripted.config.formatter.js.indent_char!==null; + + // Just do the common cases for now: + if (editor_expandtab_set || editor_tabsize_set) { + if (!(formatter_js_indent_size_set || formatter_js_indent_char_set)) { + if (editor_expandtab_set && window.scripted.config.editor.expandtab && !formatter_js_indent_char_set) { + // Set the indent char to space + if (!window.scripted.config.formatter) { + window.scripted.config.formatter = { "js": { "indent_char": " " }}; + } else if (!window.scripted.config.formatter.js) { + window.scripted.config.formatter.js = { "indent_char": " "}; + } else { + window.scripted.config.formatter.js.indent_char = " "; + } + } + if (editor_tabsize_set && !formatter_js_indent_size_set) { + // Set the indent size to match the tabsize + var tabsize = window.scripted.config.editor.tabsize; + if (!window.scripted.config.formatter) { + window.scripted.config.formatter = { "js": { "indent_size": tabsize }}; + } else if (!window.scripted.config.formatter.js) { + window.scripted.config.formatter.js = { "indent_size": tabsize}; + } else { + window.scripted.config.formatter.js.indent_size = tabsize; + } + } + } + } else { + if (formatter_js_indent_size_set || formatter_js_indent_char_set) { + var indent_char_isspace = formatter_js_indent_char_set && window.scripted.config.formatter.js.indent_char===" "; + if (indent_char_isspace) { + // Set the expandtab if we can + if (!window.scripted.config.editor) { + window.scripted.config.editor = { "expandtab": true }; + } else { + window.scripted.config.editor.expandtab = true; + } + if (formatter_js_indent_size_set) { + // Set the tabsize to match the indent size + var indentsize = window.scripted.config.formatter.js.indent_size; + if (!window.scripted.config.editor) { + window.scripted.config.editor = { "tabsize": indentsize }; + } else { + window.scripted.config.editor.tabsize = indentsize; + } + } + } + } + } + }; + + // TODO why is getConf on jsdepend? mJsdepend.getConf(filepath, function (dotScripted) { console.log("fetched dot-scripted conf from server"); @@ -121,7 +194,9 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", window.scripted.navigator=false; // default is true $('#navigator-wrapper').hide(); } - + + processConfiguration(); + // Create new FileExplorer var explorer = new mExplorerTable.FileExplorer({ //serviceRegistry: serviceRegistry, treeRoot: treeRoot, selection: selection, From fa5eb4c648163d59128c6e7443cdc39c802463bb Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 25 Oct 2012 13:37:29 -0700 Subject: [PATCH 009/931] Fix a bunch of issues with the positionioning of console. --- .scripted | 2 +- client/editor.html | 3 +- client/scripts/scripted/exec/console.css | 23 +++++++----- client/scripts/scripted/exec/exec-console.js | 37 ++++++++++++++++---- client/scripts/setup.js | 10 ++---- 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/.scripted b/.scripted index ce8f1ec6..1956dd59 100644 --- a/.scripted +++ b/.scripted @@ -96,6 +96,6 @@ */ { "jslint" : { - "global" : ["define", "require", "$"] + "global" : ["define", "require", "$", "window"] } } diff --git a/client/editor.html b/client/editor.html index d9238594..b4e2a83f 100644 --- a/client/editor.html +++ b/client/editor.html @@ -26,9 +26,10 @@
    +
    - +
    diff --git a/client/scripts/scripted/exec/console.css b/client/scripts/scripted/exec/console.css index 1e5d896f..17cfd2bc 100644 --- a/client/scripts/scripted/exec/console.css +++ b/client/scripts/scripted/exec/console.css @@ -1,33 +1,40 @@ #console_wrapper { background-color: #FFFFFF; + overflow: hide; + display: none; +} + +#console_display { overflow-x: auto; overflow-y: scroll; - display: none; - padding:6px; + position: absolute; + top: 0px; + left: 0px; + height: 100%; + width: 100%; border: 1px solid grey; } -#console_wrapper > div { +#console_display > div { background-color: background-color:rgba(0,0,0,0); white-space: pre; font-family: monospace; font-size: 11px; display: block; + margin: 5px; } .command:before { content: "> " } -#console_wrapper > .command { +#console_display > .command { font-weight:bold; - padding: 3px; border: 1px solid grey; - margin-top: 3px; - margin-bottom: 3px; + padding: 3px; } -#console_wrapper > .error { +#console_display > .error { font-weight:bold; color:red; } diff --git a/client/scripts/scripted/exec/exec-console.js b/client/scripts/scripted/exec/exec-console.js index 4459a1dd..264fa48b 100644 --- a/client/scripts/scripted/exec/exec-console.js +++ b/client/scripts/scripted/exec/exec-console.js @@ -20,7 +20,12 @@ define(["jquery", "jquery_ui"], function () { /** * The id of the dom element to which we append console output. */ - var CONSOLE_DISPLAY = "#console_wrapper"; + var CONSOLE_DISPLAY = "#console_display"; + + /** + * The id of the dom element that represents the entire console UI. + */ + var CONSOLE_WRAPPER = "#console_wrapper"; var initialized = false; @@ -38,6 +43,22 @@ define(["jquery", "jquery_ui"], function () { //////////////////// Public API /////////////////////////////////////////////////// + /** + * Change the width and vertical position of the console UI to line up properly with + * the editor. + */ + function updateWidth() { + var c = $(CONSOLE_WRAPPER); + var e = $("#editor"); + function copyCss() { + for (var i = 0; i < arguments.length; i++) { + var name = arguments[i]; + c.css(name, e.css(name)); + } + } + copyCss('margin-left', 'margin-right', 'width'); + } + /** * This method should be called by the editor setup at a good time (i.e. when editor has * mostly been setup. @@ -45,24 +66,27 @@ define(["jquery", "jquery_ui"], function () { function initialize() { if (!initialized) { initialized = true; - $(CONSOLE_DISPLAY).resizable({ + $(CONSOLE_WRAPPER).resizable({ handles: "n" }); - $(CONSOLE_DISPLAY).resize(function (event, ui) { + $(CONSOLE_WRAPPER).resize(function (event, ui) { var console_height = ui.size.height; var breadcrumb_height = $('#breadcrumb').outerHeight(); var editor_height = $('#main').height() - breadcrumb_height - console_height; - $(CONSOLE_DISPLAY).css('top', '0px'); //I think JQuery resizable is changing this from 0 but it messes things up. - $(CONSOLE_DISPLAY).height(console_height); + $(CONSOLE_WRAPPER).css('top', '0px'); //I think JQuery resizable is changing this from 0 but it messes things up. + $(CONSOLE_WRAPPER).height(console_height); $("#editor").height(editor_height); window.editor._textView._updatePage(); + updateWidth(); }); + $("#navigator-wrapper").resize(updateWidth); + $("#side_panel").resize(updateWidth); } } function show() { initialize(); - var c = $(CONSOLE_DISPLAY); + var c = $(CONSOLE_WRAPPER); var e = $("#editor"); if (c.css('display')==='none') { //If the console is presently hidden... @@ -76,6 +100,7 @@ define(["jquery", "jquery_ui"], function () { c.height(console_height-overhead); e.height(editor_height); window.editor._textView._updatePage(); + updateWidth(); } } diff --git a/client/scripts/setup.js b/client/scripts/setup.js index fce9a46e..0e25d26d 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -330,12 +330,8 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", $(window).resize(function(e){ var main_height = $(window).height() - footer_height - header_height; - var console_height; - if ($('#console_wrapper').css('display')==='none') { - console_height = 0; - } else { - console_height = $('#console_wrapper').outerHeight() || 0; - } + var console_height = ($('#console_wrapper').css('display')==='none') ? 0 : $('#console_wrapper').outerHeight() || 0; + var editor_height = main_height - breadcrumb_height - console_height; $('#main').height(main_height); $('#side_panel').height(main_height); @@ -349,7 +345,7 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", $('.subeditor_titlebar').height() ); - $('#editor').height($('#main').height() - breadcrumb_height - console_height); + $('#editor').height(editor_height); var side_width = ($('#side_panel').css('display') === 'block') ? $('#side_panel').width() : 0; $('#editor').css('margin-right', side_width); From beece45d3efabeba7186020b8aaa02a1219f273e Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Thu, 25 Oct 2012 13:54:42 -0700 Subject: [PATCH 010/931] Fix problem where window.editor may not be defined. --- client/scripts/scripted/utils/navHistory.js | 45 ++++++++++----------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index 081c6c0b..0426e5cf 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -357,43 +357,40 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD for (var i = 0, len = crumbs.length; i < len; i++) { newCrumbElem = $('
  • ' + crumbs[i] + '
  • '); + $('#breadcrumb').append(newCrumbElem); if (i + 1 === len) { constructedPath += crumbs[i]; - } else { + } + else { constructedPath += crumbs[i] + '/'; url = 'http://localhost:7261/fs_list/'+constructedPath.substring(0, constructedPath.length-1); xhrobj = new XMLHttpRequest(); xhrobj.open("GET",url,false); // TODO naughty? synchronous xhr xhrobj.send(); - if (xhrobj.status !== 200) { - i=len; // terminate early - the rest of the directory structure looks like it does not exist - } else { - $('#breadcrumb').append(newCrumbElem); - var kids = JSON.parse(xhrobj.responseText).children; - if (kids) { + var kids = JSON.parse(xhrobj.responseText).children; + if (kids) { - kids.sort(fileEntryCompare); + kids.sort(fileEntryCompare); - var newMenu = $(''); - for(var j = 0; j < kids.length; j++) { - if (kids[j].directory === false) { - if (kids[j].name.lastIndexOf('.',0)!==0) { - var href = basepath + kids[j].Location; - var newMenuItem = $('
  • '); - var newMenuAnchor = $(''+kids[j].name+''); - newMenuItem.append(newMenuAnchor); - newMenu.prepend(newMenuItem); + var newMenu = $(''); + for(var j = 0; j < kids.length; j++) { + if (kids[j].directory === false) { + if (kids[j].name.lastIndexOf('.',0)!==0) { + var href = basepath + kids[j].Location; + var newMenuItem = $('
  • '); + var newMenuAnchor = $(''+kids[j].name+''); + newMenuItem.append(newMenuAnchor); + newMenu.prepend(newMenuItem); - $(newMenuAnchor).click(navigationEventHandler); - } + $(newMenuAnchor).click(navigationEventHandler); } } - newMenu.css('left', newCrumbElem.position().left); - newMenu.css('min-width', newCrumbElem.outerWidth()); - newMenu.css('top', $('header').height() + $('#breadcrumb').height()); - $('#main').append(newMenu); } + newMenu.css('left', newCrumbElem.position().left); + newMenu.css('min-width', newCrumbElem.outerWidth()); + newMenu.css('top', $('header').height() + $('#breadcrumb').height()); + $('#main').append(newMenu); } } } @@ -657,7 +654,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD // check if the editor has been created yet, or if // window.editor is a dom node var histItem; - var hasEditor = window.editor.getText; + var hasEditor = window.editor && window.editor.getText; if (hasEditor) { histItem = generateHistoryItem(window.editor); storeScriptedHistory(histItem); From 3b5c937ff48de97f715159ccc0325ea423330882 Mon Sep 17 00:00:00 2001 From: Alain Kalker Date: Thu, 25 Oct 2012 23:03:13 +0200 Subject: [PATCH 011/931] Mark "console", "exports", "module", and "process" as global Mark these as global so they will not be flagged as errors in Node.js projects. --- .scripted | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.scripted b/.scripted index 1956dd59..ebac35d1 100644 --- a/.scripted +++ b/.scripted @@ -96,6 +96,7 @@ */ { "jslint" : { - "global" : ["define", "require", "$", "window"] + "global" : ["console", "define", "exports", "module", "process", + "require", "$", "window"] } } From d5dea16c1c8954fb9d5a2c18f4a528f50d70aed1 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 25 Oct 2012 15:57:31 -0700 Subject: [PATCH 012/931] Add a console_toggle button, and make console properly adjust its size when side-panel is opened/closed. --- .scripted | 2 +- client/css/main.css | 8 ++ client/editor.html | 1 + client/images/gnome-terminal.png | Bin 0 -> 1029 bytes client/images/gnome-terminal_16.png | Bin 0 -> 487 bytes client/scripts/scripted/exec/exec-console.js | 80 ++++++++++++++----- client/scripts/scripted/utils/navHistory.js | 2 + 7 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 client/images/gnome-terminal.png create mode 100644 client/images/gnome-terminal_16.png diff --git a/.scripted b/.scripted index 1956dd59..09046c46 100644 --- a/.scripted +++ b/.scripted @@ -96,6 +96,6 @@ */ { "jslint" : { - "global" : ["define", "require", "$", "window"] + "global" : ["define", "require", "$", "window", "document"] } } diff --git a/client/css/main.css b/client/css/main.css index 389ed87b..95f6652d 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -191,6 +191,14 @@ footer{ height:16px; } +#console_toggle { + margin: 4px 5px 0 0; + cursor:pointer; + background-image: url(../images/gnome-terminal_16.png); + width:16px; + height:16px; +} + #help_panel{ position:absolute; bottom:18px; diff --git a/client/editor.html b/client/editor.html index b4e2a83f..5a0785f7 100644 --- a/client/editor.html +++ b/client/editor.html @@ -10,6 +10,7 @@
    +
    diff --git a/client/images/gnome-terminal.png b/client/images/gnome-terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..006e5bd5ec76060bc689d2ec8baa8ce87d206fc9 GIT binary patch literal 1029 zcmV+g1p51lP)q^ZHfQt8?ny&xV_Q7{HsSUVGut zU3b4MR|RI4kc62T1_%Klzyv})pprzhghaDha{z*bBFVsH2$9VE^R2BD=RWxGqY7X= z9zFNagNN@;DPa;=4>QA30)$zb<=c>&oteHwI7mc@P@o9hGoEuZm#OQ_)vI5<0u%x% znT0!}(THo;elI84BINaG+EARRQ+|YkJ8r*?TxZ$OU-8hLP++{$~HZ>Br2fzEyTRgM&G%2MWz}F3~>38MZqHZXP_=P}mxOSXB ze~}|c9_IaXXE=KFaaLDXmj<2%lQePQDVZSL$wl>Y^*W+>cw_5tzWt8%=U-yu^ckLd z@(JEMyRkIz-eMG#kQmps09AnXES2zSL>LSP9Dd+_jvrs+ui=m@SGN8Yp9qh#KLSg| zTPq-eZ1$j2Z@kWd1GjSF!bQIL@@vN9@p5ucRA?ihH9*ledyPlTMO6jhA(MonFx=ey zjE_J0wDV??h$wc9i0(afE8^}=I69sKK&j9v1lrLJE~LQ2i-czMdc6*4?Hd+`&6%Lg zbucgS>bN_PJ^Bdi>(8>iJ#UU<$JX||^$tIXAR=gku4*Tx0Ks6;Cqm?$anF;~e(CZi zmo9Ja$o2by&ng*FQgcM$*{1|7A+RE`XU5~)?b%C$YnQ!+L@|>>69Q^I*WIb>y8BzP z9Qb~@KtXs=`&0qBhw8egJyr!J9<_L`T}q+k&q))pfynWl@&rg-fO-fcz*LF$z{ zlK|v0cSoMPFM0#juM51*A849C5{qPm9i{UEUh=NONnpn-Wd589}HtBo&}=p>PJhiW4uc6{$LUE$iye zH2cs5XVPA1CP_<}fycCTS3sDVa(4QIJ_irpi2-H_5m@Sx%u3hN9F}_3TniWqkeW)@ zG^F9h-gC~KbMFlNW7v!5PuFkXx%<*E zvk=V8rbS5=YH^a|F5I)_T+BV|-Ol^RG3R`KW##mlSFd-l5a{SSsij2m!urGe4@aK?QgPUw`zhyKUE#dW&IVB!FxJO+#AruMB`1bt=!=JlsY&=;Y&XT*FyQu40 zQSL$RT)%de$z;Oj=CdiNl!Cj@h_pf}rD;C8dyvHEFJCb;+?}}0stWdEEvH8p{j^g)vgQ&ffvW+{My@N{Ci&(V|A*ks&mUcn;G5LFl?cAcyu&A dabW)~{sQsCF){T`C{O?Z002ovPDHLkV1i9k;d%f7 literal 0 HcmV?d00001 diff --git a/client/scripts/scripted/exec/exec-console.js b/client/scripts/scripted/exec/exec-console.js index 264fa48b..3d421bd8 100644 --- a/client/scripts/scripted/exec/exec-console.js +++ b/client/scripts/scripted/exec/exec-console.js @@ -17,6 +17,12 @@ define(["jquery", "jquery_ui"], function () { + /** + * Maximum number of log entries in the console. If more entries are + * added then the oldest entries are automatically deleted. + */ + var MAX_ENTRIES = 10; + /** * The id of the dom element to which we append console output. */ @@ -28,7 +34,7 @@ define(["jquery", "jquery_ui"], function () { var CONSOLE_WRAPPER = "#console_wrapper"; var initialized = false; - + /** * A helper method that creates a dom element to display a given msg. * For now all we know how to render is plain text messages. @@ -40,6 +46,20 @@ define(["jquery", "jquery_ui"], function () { } return elem; } + + ///// Managing the entries in the log (so we can limiy their number) ////////////// + + var entries = []; //Array with at most MAX_ENTRIES entries. + var newEntryPos = 0; // next place to put an element, cycles around if running past the end. + + function addEntry(e) { + var oldest = entries[newEntryPos]; + if (oldest) { + oldest.remove(); + } + entries[newEntryPos] = e; + newEntryPos = (newEntryPos+1) % MAX_ENTRIES; + } //////////////////// Public API /////////////////////////////////////////////////// @@ -84,11 +104,16 @@ define(["jquery", "jquery_ui"], function () { } } + function isVisible() { + return $(CONSOLE_WRAPPER).css('display')!=='none'; + } + function show() { initialize(); var c = $(CONSOLE_WRAPPER); var e = $("#editor"); - if (c.css('display')==='none') { + if (!isVisible()) { + //TODO: should remember previous size not reset to 1/3 of the screen. //If the console is presently hidden... var editor_height = e.height(); var console_height = editor_height / 3; @@ -99,11 +124,37 @@ define(["jquery", "jquery_ui"], function () { c.css('display', 'block'); c.height(console_height-overhead); e.height(editor_height); + c.resizable({ + disabled: false + }); + window.editor._textView._updatePage(); updateWidth(); } } + function hide() { + if (isVisible()) { + var c = $(CONSOLE_WRAPPER); + var e = $("#editor"); + var console_height = c.outerHeight(); + c.css('display', 'none'); + c.resizable({ + disabled: true + }); + e.height(e.height()+console_height); + window.editor._textView._updatePage(); + } + } + + function toggle() { + if (isVisible()) { + hide(); + } else { + show(); + } + } + function scrollToBottom() { var c = $(CONSOLE_DISPLAY); var scrollH = c.prop("scrollHeight"); @@ -127,26 +178,15 @@ define(["jquery", "jquery_ui"], function () { show(); var e = render(msg, cssClass); $(CONSOLE_DISPLAY).append(e); + addEntry(e); scrollToBottom(); } - - function hide() { - //TODO: this code is not really correct. -// var c = $(CONSOLE_DISPLAY); -// var e = $("#editor"); -// if (c.css('display')==='block') { -// //If the console is presently shown... -// var editor_height = e.height(); -// var console_height = c.outerHeight(); -// var padding = consoleHeig -// editor_height = editor_height - console_height; -// -// c.css('display', 'block'); -// c.height(console_height); -// e.height(editor_height); -// window.editor._textView._updatePage(); -// } - } + + $(document).ready(function () { + $('#console_toggle').on('click', toggle); + $('#side_panel').bind('open', updateWidth); + $('#side_panel').bind('close', updateWidth); + }); return { log: log, diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index 0426e5cf..31501dfe 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -48,6 +48,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD $('#side_panel').hide(); $('#editor').css('margin-right', '0'); editor._textView._updatePage(); + $('#side_panel').trigger('close'); }; var open_side = function(editor) { @@ -55,6 +56,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD $('#side_panel').show(); $('#editor').css('margin-right', $('#side_panel').width()); editor._textView._updatePage(); + $('#side_panel').trigger('open'); }; var scrollDefinition = function(editor) { From eb92f40cf715ee2162b2b6a4a8d9e5b89fc8e914 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 08:45:57 -0700 Subject: [PATCH 013/931] SCRIPTED-210 CMD Shift click navigation in editor --- .../scripts/scripted/editor/scriptedEditor.js | 26 +-- client/scripts/scripted/utils/navHistory.js | 188 +++++++++++------- client/scripts/scripted/utils/os.js | 24 +++ client/scripts/setup.js | 24 +-- 4 files changed, 161 insertions(+), 101 deletions(-) create mode 100644 client/scripts/scripted/utils/os.js diff --git a/client/scripts/scripted/editor/scriptedEditor.js b/client/scripts/scripted/editor/scriptedEditor.js index 96f01c74..75ab6c02 100644 --- a/client/scripts/scripted/editor/scriptedEditor.js +++ b/client/scripts/scripted/editor/scriptedEditor.js @@ -196,19 +196,19 @@ mHtmlContentAssist, mCssContentAssist) { var annotationFactory = new mEditorFeatures.AnnotationFactory(); /* for some reason, jsbeautify likes to strip the first line of its indent. let's fix that */ - var fixFirstLineFormatting = function(toFormat, formatted) { - var fix_format = ""; - var i = 0; - var char = toFormat.charAt(i); - var format_char = formatted.charAt(0); - while (char !== format_char) { - fix_format = char + fix_format; - i++; - char = toFormat.charAt(i); - } - formatted = fix_format + formatted; - return formatted; - }; +// var fixFirstLineFormatting = function(toFormat, formatted) { +// var fix_format = ""; +// var i = 0; +// var char = toFormat.charAt(i); +// var format_char = formatted.charAt(0); +// while (char !== format_char) { +// fix_format = char + fix_format; +// i++; +// char = toFormat.charAt(i); +// } +// formatted = fix_format + formatted; +// return formatted; +// }; var keyBindingFactory = function(editor, keyModeStack, undoStack, contentAssist) { diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index 31501dfe..b7914e68 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -16,9 +16,9 @@ /*jslint browser:true */ /*global window setTimeout define explorer document console location XMLHttpRequest alert confirm orion scripted dojo $ localStorage*/ define(["scripted/editor/scriptedEditor", "orion/textview/keyBinding", "orion/searchClient", "scripted/widgets/OpenResourceDialog", "scripted/widgets/OpenOutlineDialog", -"scripted/fileSearchClient", "scripted/widgets/SearchDialog"], +"scripted/fileSearchClient", "scripted/widgets/SearchDialog", "scripted/utils/os"], function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineDialog, - mFileSearchClient, mSearchDialog) { + mFileSearchClient, mSearchDialog, mOsUtils) { var EDITOR_TARGET = { main : "main", @@ -30,11 +30,9 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD // define as forward reference var navigate; - var isMac = navigator.platform.indexOf("Mac") !== -1; - var findTarget = function(event) { var target; - if ((isMac && event.metaKey) || (!isMac && event.ctrlKey)) { + if (mOsUtils.isCtrlOrMeta(event)) { target = EDITOR_TARGET.tab; } else { var subNavigationDisabled = (window.editor.loadResponse === 'error') ? true : false; @@ -257,6 +255,48 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } }; + /** + * @param {{String|Object}} modifier either EDITOR_TARGET.main, EDITOR_TARGET.sub, or EDITOR_TARGET.tab + * @param {{range:List.,path:String}} definition + * @param {{Editor}} editor + */ + var openOnRange = function(modifier, definition, editor) { + if (!definition.range && !definition.path) { + return; + } + var defnrange = definition.range ? definition.range : editor.getSelection(); + var filepath = definition.path ? definition.path : editor.getFilePath(); + + console.log("navigation: "+JSON.stringify({path: filepath, range: defnrange})); + + var target; + if (typeof modifier === "object") { + target = findTarget(modifier); + } else if (typeof modifier === "string") { + target = modifier; + } + if (target) { + navigate(filepath, defnrange, target, true); + } + }; + + function openOnClick(event, editor) { + if (mOsUtils.isCtrlOrMeta(event)) { + var rect = editor.getTextView().convert({x:event.pageX, y:event.pageY}, "page", "document"); + var offset = editor.getTextView().getOffsetAtLocation(rect.x, rect.y); + var definition = editor.findDefinition(offset); + if (definition) { + openOnRange(event.shiftKey ? "sub" : "main", definition, editor); + } + } + } + + var buildMaineditor = function() { + $('#editor').click(function(event) { + openOnClick(event, window.editor); + }); + }; + var buildSubeditor = function(filepath) { var filename = filepath.split('/').pop(); @@ -283,6 +323,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD $('.subeditor_titlebar').height() ); + // must reattach these handlers on every new subeditor open since we always delete the old editor $('.subeditor_close').click(function() { if (window.subeditors[0] && confirmNavigation(window.subeditors[0])) { $('.subeditor_wrapper').remove(); @@ -293,7 +334,10 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }); $('.subeditor_switch').click(switchEditors); - + + $('.subeditor').click(function(event) { + openOnClick(event, window.subeditors[0]); + }); return subeditor; }; @@ -496,31 +540,8 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }); }; - /** - * @param {{String|Object}} modifier either EDITOR_TARGET.main, EDITOR_TARGET.sub, or EDITOR_TARGET.tab - * @param {{range:List.,path:String}} definition - * @param {{Editor}} editor - */ - var openDeclaration = function(modifier, definition, editor) { - if (!definition.range && !definition.path) { - return; - } - var defnrange = definition.range ? definition.range : editor.getSelection(); - var filepath = definition.path ? definition.path : editor.getFilePath(); - - console.log("navigation: "+JSON.stringify({path: filepath, range: defnrange})); - - var target; - if (typeof modifier === "object") { - target = findTarget(modifier); - } else if (typeof modifier === "string") { - target = modifier; - } - if (target) { - navigate(filepath, defnrange, target, true); - } - }; + // TODO move to scriptedEditor.js var attachFileSearchClient = function(editor) { var fileSearcher = new mFileSearchClient.FileSearcher({ @@ -532,7 +553,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD fileSearcher: fileSearcher, fileSearchRenderer: fileSearcher.defaultRenderer, style:"width:800px", - openDeclaration: openDeclaration + openOnRange: openOnRange }); //TODO we should explicitly set focus to the previously active editor if the dialog has been canceled @@ -552,30 +573,33 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }); }; + + // TODO move to scriptedEditor.js var attachDefinitionNavigation = function(editor) { editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ false, /*shift*/ false, /*alt*/ false), "Open declaration"); editor.getTextView().setAction("Open declaration", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - openDeclaration(EDITOR_TARGET.main, definition, editor); + openOnRange(EDITOR_TARGET.main, definition, editor); } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ true, /*shift*/ false, /*alt*/ false), "Open declaration in new tab"); editor.getTextView().setAction("Open declaration in new tab", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - openDeclaration(EDITOR_TARGET.tab, definition, editor); + openOnRange(EDITOR_TARGET.tab, definition, editor); } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ false, /*shift*/ true, /*alt*/ false), "Open declaration in subeditor"); editor.getTextView().setAction("Open declaration in subeditor", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - openDeclaration(EDITOR_TARGET.sub, definition, editor); + openOnRange(EDITOR_TARGET.sub, definition, editor); } }); }; + // TODO move to scriptedEditor.js var attachEditorSwitch = function(editor) { editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding("s", /*command/ctrl*/ true, /*shift*/ true, /*alt*/ false), "Switch Subeditor and Main Editor"); editor.getTextView().setAction("Switch Subeditor and Main Editor", switchEditors); @@ -603,12 +627,15 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD $('.subeditor_close').click(); return editor; } + + // TODO move to scriptedEditor.js attachSearchClient(editor); attachOutlineClient(editor); attachDefinitionNavigation(editor); attachFileSearchClient(editor); attachEditorSwitch(editor); editor.cursorFix(); + if (type === 'main') { setTimeout(function() { editor.getTextView().focus(); @@ -653,64 +680,78 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD * @return {boolean} true if navigation occurred successfully and false otherwise. */ navigate = function(filepath, range, target, doSaveState) { + var histItem; // check if the editor has been created yet, or if // window.editor is a dom node - var histItem; - var hasEditor = window.editor && window.editor.getText; - if (hasEditor) { + var hasMainEditor = window.editor && window.editor.getText; + var hasSubEditpr = hasMainEditor && window.subeditors.length > 0; + if (hasMainEditor) { histItem = generateHistoryItem(window.editor); storeScriptedHistory(histItem); if (doSaveState) { storeBrowserState(histItem, true); } - if (window.subeditors[0]) { - var subHistItem = generateHistoryItem(window.subeditors[0]); - storeScriptedHistory(subHistItem); + if (hasSubEditpr) { + histItem = generateHistoryItem(window.subeditors[0]); + storeScriptedHistory(histItem); } } - if (target === EDITOR_TARGET.sub) { - if (hasEditor && !confirmNavigation(window.subeditors[0])) { + + if (target === EDITOR_TARGET.sub || target === EDITOR_TARGET.main) { + var targetEditor = target === EDITOR_TARGET.main ? window.editor : window.subeditors[0]; + var hasEditor = targetEditor && targetEditor.getText; + var isSame = hasEditor && targetEditor.getFilePath() === filepath; + if (!isSame && hasEditor && !confirmNavigation(window.editor)) { return false; } - open_side(window.editor); - $('.subeditor_wrapper').remove(); - buildSubeditor(filepath); - window.subeditors[0] = loadEditor(filepath, $('.subeditor')[0], 'sub'); + + // this is annoying...the targetEditor is destroyed and recreated here so can't get the dom node undil after this if statement + if (target === EDITOR_TARGET.sub && !isSame) { + open_side(window.editor); + $('.subeditor_wrapper').remove(); + buildSubeditor(filepath); + } + var domNode = target === EDITOR_TARGET.main ? $('#editor') : $('.subeditor'); + if (target === EDITOR_TARGET.main) { + if (!hasEditor) { + buildMaineditor(filepath); + } + domNode.css('display','block'); + } + + if (!hasEditor || !isSame) { + targetEditor = loadEditor(filepath, domNode[0], target); + } + if (range) { if (isNaN(range[0]) || isNaN(range[1])) { console.log("invalid range"); console.log(range); } - window.subeditors[0].getTextView().setSelection(range[0], range[1], true); - scrollDefinition(window.subeditors[0]); - } - initializeHistoryMenu(); - window.subeditors[0].getTextView().focus(); - } else if (target === EDITOR_TARGET.main) { - if (hasEditor && !confirmNavigation(window.editor)) { - return false; - } - $('#editor').css('display','block'); - window.editor = loadEditor(filepath, $('#editor')[0], EDITOR_TARGET.main); - if (range) { - window.editor.getTextView().setSelection(range[0], range[1], false); - scrollDefinition(window.editor); + targetEditor.getTextView().setSelection(range[0], range[1], true); + scrollDefinition(targetEditor); } - // explicit check for false since navigator might be 'undefined' at this point - if (window.scripted.navigator !== false) { - // if model not yet available, highlighting is handled elsewhere. - if (explorer.model) { - explorer.highlight(filepath); + if (target === EDITOR_TARGET.main) { + // explicit check for false since navigator might be 'undefined' at this point + if (window.scripted.navigator !== false) { + // if model not yet available, highlighting is handled elsewhere. + if (explorer.model) { + explorer.highlight(filepath); + } } + initializeBreadcrumbs(filepath); + if (doSaveState) { + histItem = generateHistoryItem(targetEditor); + storeBrowserState(histItem); + } + window.editor = targetEditor; + } else { + window.subeditors[0] = targetEditor; } - initializeBreadcrumbs(filepath); - window.editor.getTextView().focus(); - if (doSaveState) { - histItem = generateHistoryItem(window.editor); - storeBrowserState(histItem); - } - + initializeHistoryMenu(); + targetEditor.getTextView().focus(); + } else if (target === EDITOR_TARGET.tab) { var targetPath = range ? filepath + "#" + range : filepath; var rootpath = window.location.protocol + "//" + window.location.host + window.location.pathname + '?'; @@ -719,11 +760,12 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD return false; }; - + return { // loadEditor is a private function and only exposed for testing purposes _loadEditor: loadEditor, + openOnRange: openOnRange, initializeBreadcrumbs: initializeBreadcrumbs, navigationEventHandler: navigationEventHandler, highlightSelection: highlightSelection, diff --git a/client/scripts/scripted/utils/os.js b/client/scripts/scripted/utils/os.js new file mode 100644 index 00000000..9ad86c17 --- /dev/null +++ b/client/scripts/scripted/utils/os.js @@ -0,0 +1,24 @@ +/******************************************************************************* + * @license + * Copyright (c) 2012 VMware, Inc. All Rights Reserved. + * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE + * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE + * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT. + * You can obtain a current copy of the Eclipse Public License from + * http://www.opensource.org/licenses/eclipse-1.0.php + * + * Contributors: + * Andrew Eisenberg - refactoring for a more consistent approach to navigation + ******************************************************************************/ +/** + * this module provides OS specific utilities + */ + /*jslint browser:true */ + +define({ + // this function is re-defined in many places, but most are + isMac : navigator.platform.indexOf("Mac") !== -1, + isCtrlOrMeta : function(event) { + return (this.isMac && event.metaKey) || (!this.isMac && event.ctrlKey); + } +}); \ No newline at end of file diff --git a/client/scripts/setup.js b/client/scripts/setup.js index bc5e9718..552cc151 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -102,9 +102,10 @@ requirejs.config({ }); require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", "fileapi", "orion/textview/keyBinding", "orion/searchClient", - "scripted/widgets/OpenResourceDialog", "jquery", "scripted/utils/navHistory", "servlets/jsdepend-client", "scripted/exec/exec-on-load"], + "scripted/widgets/OpenResourceDialog", "jquery", "scripted/utils/navHistory", "servlets/jsdepend-client", "scripted/utils/os", + "scripted/exec/exec-on-load"], - function(mEditor, mExplorerTable, mFileApi, mKeyBinding, mSearchClient, mOpenResourceDialog, mJquery, mNavHistory, mJsdepend) { + function(mEditor, mExplorerTable, mFileApi, mKeyBinding, mSearchClient, mOpenResourceDialog, mJquery, mNavHistory, mJsdepend, mOsUtils) { if (!window.scripted) { window.scripted = {}; @@ -208,12 +209,6 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", }); window.explorer = explorer; -// window.editor = mNavHistory.loadEditor( filepath, ($('#editor')[0]), 'main' ); -// if (location.hash.length > 1) { -// mNavHistory.highlightSelection(window.editor); -// } -// -// mNavHistory.initializeBreadcrumbs(filepath); var range; try { range = JSON.parse('[' + location.hash.substring(1) +']'); @@ -239,7 +234,6 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", require(['jquery_ui'], function(mJqueryUI){ /*Resizable navigator*/ - var navigator_width = $('#navigator-container').width(); $('#navigator-wrapper').resizable({ handles: "e" }); @@ -306,11 +300,12 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", } }); - $.views.helpers({ - isMac: function(){ - return (window.navigator.platform.indexOf("Mac") !== -1); - } - }); + // TODO FIXADE I think we can delete +// $.views.helpers({ +// isMac: function(){ +// return (window.navigator.platform.indexOf("Mac") !== -1); +// } +// }); var command_file = "../resources/_command.tmpl.html"; // use a copy so we can sort @@ -447,7 +442,6 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", } }; -// $(window).load(function(){ $(document).ready(function(){ require("scripted/exec/exec-on-load").installOn(window.fsroot); /* setTimeout so popstate doesn't fire on initial page load */ From 4fdf5a4fa39c6bd99eb1ad98fb779e5cf5c5e6cd Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 26 Oct 2012 10:48:44 -0700 Subject: [PATCH 014/931] The vertical postioning/width of exec-console needs to be adjusted if the window is resized (vertically). --- client/scripts/scripted/exec/exec-console.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/scripts/scripted/exec/exec-console.js b/client/scripts/scripted/exec/exec-console.js index 3d421bd8..3bc0f569 100644 --- a/client/scripts/scripted/exec/exec-console.js +++ b/client/scripts/scripted/exec/exec-console.js @@ -186,6 +186,7 @@ define(["jquery", "jquery_ui"], function () { $('#console_toggle').on('click', toggle); $('#side_panel').bind('open', updateWidth); $('#side_panel').bind('close', updateWidth); + $(window).resize(updateWidth); }); return { From 1015718a404fcd1f106c47408e8ad117f47aa7f3 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 11:34:37 -0700 Subject: [PATCH 015/931] Make client-server tests pass again by making the server more tolerant of urls that are bad. --- .../js-tests/navHIstory/navHistoryTests.js | 30 ++++++++++- .../js-tests/scriptedClientServerTests.html | 2 +- .../scripts/scripted/editor/scriptedEditor.js | 12 +++-- client/scripts/scripted/utils/navHistory.js | 53 ++++++++++--------- client/scripts/setup.js | 2 +- server/requestHandlers.js | 26 +++++++-- 6 files changed, 86 insertions(+), 39 deletions(-) diff --git a/client/scripts/js-tests/navHIstory/navHistoryTests.js b/client/scripts/js-tests/navHIstory/navHistoryTests.js index 010319f9..3f7af76e 100644 --- a/client/scripts/js-tests/navHIstory/navHistoryTests.js +++ b/client/scripts/js-tests/navHIstory/navHistoryTests.js @@ -44,6 +44,8 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio function setup() { window.fsroot = testResourcesRootNoSlash; localStorage.removeItem("scriptedHistory"); + $('.subeditor_wrapper').remove(); + window.subeditors = []; window.editor = mNavHistory._loadEditor(testResourcesRoot + "foo.js"); mNavHistory.initializeBreadcrumbs(testResourcesRoot + "foo.js"); } @@ -70,13 +72,13 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio }; tests.asyncTestToggleSidePanel = function() { setup(); - assert.ok(!window.subeditors[0]); + assert.ok(!window.subeditors[0], window.subeditors[0]); $('#side_panel').css('display', 'none'); mNavHistory.toggleSidePanel(); setTimeout(function() { assert.ok(window.subeditors[0]); - assert.ok(window.editor.getText(), window.subeditors[0].getText()); + assert.equal(window.editor.getText(), window.subeditors[0].getText()); mNavHistory.toggleSidePanel(); setTimeout(function() { assert.ok(!window.subeditors[0]); @@ -197,10 +199,34 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio assert.equal(historyMenu.children()[2].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#15,25"); }; + tests.asyncTestGetContentsSubEditor = function() { + setup(); + setTimeout(function() { + assert.ok(window.editor); + assert.ok(!window.subeditors[0]); + $('#side_panel').css('display', 'none'); + mNavHistory.toggleSidePanel(); + assert.ok(window.subeditors[0]); + + getFileContents("foo.js", + function(contents) { + window.subeditors[0] = mNavHistory._loadEditor(testResourcesRoot + "foo.js", $('.subeditor')[0], "sub"); + assert.equal(window.subeditors[0].getText(), contents); + + getFileContents("bar.js", function(contents) { + window.subeditors[0] = mNavHistory._loadEditor(testResourcesRoot + "bar.js", $('.subeditor')[0], "sub"); + assert.equal(window.subeditors[0].getText(), contents); + assert.start(); + }); + }); + }, 500); + }; + // still to test // raw history object // subeditor and state + // open on range return tests; }); \ No newline at end of file diff --git a/client/scripts/js-tests/scriptedClientServerTests.html b/client/scripts/js-tests/scriptedClientServerTests.html index 7ec6cd97..d155a136 100644 --- a/client/scripts/js-tests/scriptedClientServerTests.html +++ b/client/scripts/js-tests/scriptedClientServerTests.html @@ -3,7 +3,7 @@ - Inferencing tests + Client tests that require a server diff --git a/client/scripts/scripted/editor/scriptedEditor.js b/client/scripts/scripted/editor/scriptedEditor.js index 75ab6c02..fbc56d91 100644 --- a/client/scripts/scripted/editor/scriptedEditor.js +++ b/client/scripts/scripted/editor/scriptedEditor.js @@ -169,11 +169,13 @@ mHtmlContentAssist, mCssContentAssist) { model: new mProjectionModel.ProjectionTextModel(new mTextModel.TextModel()), tabSize: 4 }; - if (window.scripted.config.editor && window.scripted.config.editor.expandtab) { - options.expandTab = window.scripted.config.editor.expandtab; - } - if (window.scripted.config.editor && window.scripted.config.editor.tabsize) { - options.tabSize = window.scripted.config.editor.tabsize; + if (window.scripted.config) { + if (window.scripted.config.editor && window.scripted.config.editor.expandtab) { + options.expandTab = window.scripted.config.editor.expandtab; + } + if (window.scripted.config.editor && window.scripted.config.editor.tabsize) { + options.tabSize = window.scripted.config.editor.tabsize; + } } return new mTextView.TextView(options); }; diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index b7914e68..d031ba87 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -241,26 +241,28 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } }; - var highlightSelection = function(editor) { - try{ - var loc = JSON.parse("[" + location.hash.substring(1) + "]"); - if (loc && loc.constructor === Array && loc.length > 0) { - var start = loc[0]; - var end = loc.length > 1 ? loc[1] : start; - editor.getTextView().setSelection(start, end, true); - scrollDefinition(editor); - } - } catch (e) { - console.log("Could not navigate to location specified in hash. Hash value: " + location.hash); - } - }; + + // don't think we need this any more +// var highlightSelection = function(editor) { +// try{ +// var loc = JSON.parse("[" + location.hash.substring(1) + "]"); +// if (loc && loc.constructor === Array && loc.length > 0) { +// var start = loc[0]; +// var end = loc.length > 1 ? loc[1] : start; +// editor.getTextView().setSelection(start, end, true); +// scrollDefinition(editor); +// } +// } catch (e) { +// console.log("Could not navigate to location specified in hash. Hash value: " + location.hash); +// } +// }; /** * @param {{String|Object}} modifier either EDITOR_TARGET.main, EDITOR_TARGET.sub, or EDITOR_TARGET.tab * @param {{range:List.,path:String}} definition * @param {{Editor}} editor */ - var openOnRange = function(modifier, definition, editor) { + var _openOnRange = function(modifier, definition, editor) { if (!definition.range && !definition.path) { return; } @@ -286,7 +288,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD var offset = editor.getTextView().getOffsetAtLocation(rect.x, rect.y); var definition = editor.findDefinition(offset); if (definition) { - openOnRange(event.shiftKey ? "sub" : "main", definition, editor); + _openOnRange(event.shiftKey ? "sub" : "main", definition, editor); } } } @@ -407,8 +409,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD if (i + 1 === len) { constructedPath += crumbs[i]; - } - else { + } else { constructedPath += crumbs[i] + '/'; url = 'http://localhost:7261/fs_list/'+constructedPath.substring(0, constructedPath.length-1); xhrobj = new XMLHttpRequest(); @@ -553,7 +554,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD fileSearcher: fileSearcher, fileSearchRenderer: fileSearcher.defaultRenderer, style:"width:800px", - openOnRange: openOnRange + _openOnRange: _openOnRange }); //TODO we should explicitly set focus to the previously active editor if the dialog has been canceled @@ -580,21 +581,21 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD editor.getTextView().setAction("Open declaration", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - openOnRange(EDITOR_TARGET.main, definition, editor); + _openOnRange(EDITOR_TARGET.main, definition, editor); } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ true, /*shift*/ false, /*alt*/ false), "Open declaration in new tab"); editor.getTextView().setAction("Open declaration in new tab", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - openOnRange(EDITOR_TARGET.tab, definition, editor); + _openOnRange(EDITOR_TARGET.tab, definition, editor); } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ false, /*shift*/ true, /*alt*/ false), "Open declaration in subeditor"); editor.getTextView().setAction("Open declaration in subeditor", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - openOnRange(EDITOR_TARGET.sub, definition, editor); + _openOnRange(EDITOR_TARGET.sub, definition, editor); } }); }; @@ -741,15 +742,15 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } } initializeBreadcrumbs(filepath); + window.editor = targetEditor; if (doSaveState) { histItem = generateHistoryItem(targetEditor); storeBrowserState(histItem); } - window.editor = targetEditor; } else { window.subeditors[0] = targetEditor; + initializeHistoryMenu(); } - initializeHistoryMenu(); targetEditor.getTextView().focus(); } else if (target === EDITOR_TARGET.tab) { @@ -762,13 +763,13 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }; return { - // loadEditor is a private function and only exposed for testing purposes + // private functions that are only exported to help with testing _loadEditor: loadEditor, + _openOnRange: _openOnRange, - openOnRange: openOnRange, initializeBreadcrumbs: initializeBreadcrumbs, navigationEventHandler: navigationEventHandler, - highlightSelection: highlightSelection, +// highlightSelection: highlightSelection, don't think we need this popstateHandler: popstateHandler, toggleSidePanel: toggleSidePanel, navigate: navigate diff --git a/client/scripts/setup.js b/client/scripts/setup.js index 552cc151..f87d8ff1 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -307,7 +307,7 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", // } // }); - var command_file = "../resources/_command.tmpl.html"; + var command_file = "resources/_command.tmpl.html"; // use a copy so we can sort var keyBindings = window.editor.getTextView()._keyBindings.slice(0); diff --git a/server/requestHandlers.js b/server/requestHandlers.js index e9dcb054..fa03aff8 100644 --- a/server/requestHandlers.js +++ b/server/requestHandlers.js @@ -94,6 +94,16 @@ function get(response, request) { function get2(response, request) { var file = url.parse(request.url,true).query.file; //console.log("Processing get request for "+file); + if (!file) { + // no file passed in + response.writeHead(500, { + "Content-Type": "text/plain" + }); + response.write("No file name passed in"); + response.end(); + return; + } + fs.readFile(file, function(err,data){ if(err) { if (err.errno === 28 /*EISDIR*/) { @@ -185,10 +195,18 @@ function fs_list(response, request, path) { if (path) { pathToUse = path; } else { - var obj2 = url.parse(request.url,true).query; - //console.log("fsq: request url query is "+url.parse(request.url,true).query); - var data = JSON.parse(obj2.query); - pathToUse = data.name; + var obj2; + try { + obj2 = url.parse(request.url,true).query; + //console.log("fsq: request url query is "+url.parse(request.url,true).query); + var data = JSON.parse(obj2.query); + pathToUse = data.name; + } catch (e) { + response.writeHead(500, {'content-type': 'text/plain'}); + response.write('Invalid path request '+obj2); + response.end(); + return; + } } //console.log("fs_list request for: "+pathToUse); From e7ab81e5fea6a9905cea1161c314ad77d7df6cb5 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 26 Oct 2012 12:06:44 -0700 Subject: [PATCH 016/931] Disable some logging code --- client/scripts/setup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/scripts/setup.js b/client/scripts/setup.js index 552cc151..4eec8b8a 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -187,8 +187,8 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", // TODO why is getConf on jsdepend? mJsdepend.getConf(filepath, function (dotScripted) { - console.log("fetched dot-scripted conf from server"); - console.log(JSON.stringify(dotScripted, null, " ")); +// console.log("fetched dot-scripted conf from server"); +// console.log(JSON.stringify(dotScripted, null, " ")); window.fsroot = dotScripted.fsroot; window.scripted.config = dotScripted; if (window.scripted.config && window.scripted.config.ui && window.scripted.config.ui.navigator===false) { From 4d5b09b701978690058ebf313ca9105c83630e5c Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Fri, 26 Oct 2012 12:23:00 -0700 Subject: [PATCH 017/931] Better dialog for goto line These changes add a dialogs module for common dialog handling and goto line is the first and simplest dialog built using it. The css needs further tidying I think, and perhaps the mess of id/class entries in the html. Issue: SCRIPTED-96 --- client/css/main.css | 88 +++++++++++++++++ client/editor.html | 13 +++ client/scripts/orion/editor/editorFeatures.js | 10 +- client/scripts/scripted/editor/dialogs.js | 99 +++++++++++++++++++ 4 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 client/scripts/scripted/editor/dialogs.js diff --git a/client/css/main.css b/client/css/main.css index 95f6652d..ddfaa92f 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -531,3 +531,91 @@ footer{ .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;} .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +#dialog_mask { + width:100%; + height:100%; + filter:alpha(opacity=50%) + -moz-opacity:0.5; + -khtml-opacity:0.5; + opacity:0.5; + background:#000; + position:absolute; + top:0; + left:0; + z-index:3000; + display:none; +} + +#dialog_goto_line { + -webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); + + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + background:#eee; + // currently a fixed width: + width:320px; + + /* high z index means on top*/ + position:absolute; + z-index:5000; + + /* hidden by default */ + display:none; +} + +a.dialog_button { + margin:10px auto 5px auto; + text-align:center; + display: block; + width:50px; + padding: 5px 10px 6px; + color: #fff; + text-decoration: none; + font-weight: bold; + line-height: 1; + + /* button color */ + background-color: #323232; + + /* rounded corner */ + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + + /* text shadow */ + text-shadow: 0 -1px 1px rgba(0,0,0,0.25); + border-bottom: 1px solid rgba(0,0,0,0.25); + position: relative; + cursor: pointer; + +} + +a.dialog_button:hover { + background-color: #4f4f4f; +} + +#dialog_goto_line .dialog_content { + font-family:arial; + font-size:13px; + text-align:left; + margin:13px; + padding:10px; + color:#666; +} + +#dialog_goto_line .dialog_content p { + font-weight:600; + margin:0; +} + +#dialog_goto_line .dialog_content ul { + margin:10px 0 10px 20px; + padding:0; + height:50px; +} \ No newline at end of file diff --git a/client/editor.html b/client/editor.html index 5a0785f7..3e9b2d78 100644 --- a/client/editor.html +++ b/client/editor.html @@ -45,5 +45,18 @@
    + +
    + +
    + +
    +
    +

    Enter destination line:

    + +
    + OK +
    +
    diff --git a/client/scripts/orion/editor/editorFeatures.js b/client/scripts/orion/editor/editorFeatures.js index 92efed72..0faee6d5 100644 --- a/client/scripts/orion/editor/editorFeatures.js +++ b/client/scripts/orion/editor/editorFeatures.js @@ -13,8 +13,8 @@ /*jslint maxerr:150 browser:true devel:true */ define("orion/editor/editorFeatures", ['i18n!orion/editor/nls/messages', 'orion/textview/undoStack', 'orion/textview/keyBinding', - 'orion/textview/rulers', 'orion/textview/annotations', 'orion/textview/textDND', 'orion/editor/regex', 'orion/textview/util'], -function(messages, mUndoStack, mKeyBinding, mRulers, mAnnotations, mTextDND, mRegex, mUtil) { + 'orion/textview/rulers', 'orion/textview/annotations', 'orion/textview/textDND', 'orion/editor/regex', 'orion/textview/util','scripted/editor/dialogs'], +function(messages, mUndoStack, mKeyBinding, mRulers, mAnnotations, mTextDND, mRegex, mUtil, mDialogs) { function UndoFactory() { } @@ -471,11 +471,9 @@ function(messages, mUndoStack, mKeyBinding, mRulers, mAnnotations, mTextDND, mRe var editor = this.editor; var model = editor.getModel(); var line = model.getLineAtOffset(editor.getCaretOffset()); - line = prompt(messages.gotoLinePrompty, line + 1); - if (line) { - line = parseInt(line, 10); + mDialogs.openDialog_gotoLine(line+1,function(line) { editor.onGotoLine(line - 1, 0); - } + }); return true; }.bind(this)); diff --git a/client/scripts/scripted/editor/dialogs.js b/client/scripts/scripted/editor/dialogs.js new file mode 100644 index 00000000..fde63325 --- /dev/null +++ b/client/scripts/scripted/editor/dialogs.js @@ -0,0 +1,99 @@ +/******************************************************************************* + * @license + * Copyright (c) 2012 - 2012 VMware and others. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * Andy Clement + *******************************************************************************/ +/*global define window $*/ +/*jslint browser:true*/ + +/** + * This module is responsible for dialogs - creating them, positioning them, showing/hiding them. + */ +define([],function() { + + /** + * Show (or resize) the mask and a particular dialog (e.g. '#dialog_goto_line'). The sizes are computed + * such that the mask fills the screen. + */ + var popupOrResizeMaskAndDialog = function(dialogId) { + // get the screen height and width + var maskHeight = $(document).height(); + var maskWidth = $(document).width(); + + // calculate the values for center alignment + var dialogTop = (maskHeight/3) - ($(dialogId).height()); + var dialogLeft = (maskWidth/2) - ($(dialogId).width()/2); + + // assign values to the overlay and dialog box + $('#dialog_mask').css({height:maskHeight, width:maskWidth}).show(); + $(dialogId).css({top:dialogTop, left:dialogLeft}).show(); + }; + + // One off event registration (doesn't need to be repeated): + + // Clicking in the dialog will focus the line number field + $('#dialog_goto_line').on('click.dialogs',function() { + $('#goto_line_number').focus(); + return false; + }); + + var openDialog_gotoLine = function(line, onclose) { + var activeElement = document.activeElement; + + // Clicking the mask will hide it and the dialog + $('#dialog_mask').off('click.dialogs'); + $('#dialog_mask').on('click.dialogs',function() { + $('#dialog_mask').hide(); + $('#dialog_goto_line').hide(); + $(activeElement).focus(); + return false; + }); + + // Handle ENTER and ESCAPE keypresses on the dialog + $('#dialog_goto_line').off('keyp.dialogs'); + $('#dialog_goto_line').on('keyup.dialogs',function( e ) { + // Pressing ENTER triggers the button click + if( e.keyCode === $.ui.keyCode.ENTER ) { + $('#dialog_goto_line_button').trigger( 'click' ); + } + // Pressing ESCAPE closes the dialog (and mask) and refocuses to the original element + if (e.keyCode === $.ui.keyCode.ESCAPE ) { + $('#dialog_mask,#dialog_goto_line').hide(); + $(activeElement).focus(); + } + }); + + // Handle button clicks + $('#dialog_goto_line_button').off('click.dialogs'); + $('#dialog_goto_line_button').on('click.dialogs',function() { + var line = parseInt($('#goto_line_number').val(),10); + $('#dialog_mask,#dialog_goto_line').hide(); + onclose(line); + $(activeElement).focus(); + return false; + }); + + // Handle resize events - adjust size of mask and dialog + $(window).off('resize.dialogs'); + $(window).on('resize.dialogs',function() { + if (!$('#dialog_mask').is(':hidden')) { + popupOrResizeMaskAndDialog('#dialog_goto_line'); + } + }); + + popupOrResizeMaskAndDialog("#dialog_goto_line"); + $('#goto_line_number').val(line); + $('#goto_line_number').focus(); + $('#goto_line_number').select(); + }; + + return { + openDialog_gotoLine: openDialog_gotoLine + }; +}); From ce0047bef38ce70a91d39a92ea1e59add6f6f93a Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 14:02:20 -0700 Subject: [PATCH 018/931] ensure that the correct function is called on dialog close. --- client/scripts/scripted/utils/navHistory.js | 16 ++++++++-------- .../scripted/widgets/OpenOutlineDialog.js | 2 +- client/scripts/scripted/widgets/SearchDialog.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index d031ba87..48472b99 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -262,7 +262,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD * @param {{range:List.,path:String}} definition * @param {{Editor}} editor */ - var _openOnRange = function(modifier, definition, editor) { + var openOnRange = function(modifier, definition, editor) { if (!definition.range && !definition.path) { return; } @@ -288,7 +288,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD var offset = editor.getTextView().getOffsetAtLocation(rect.x, rect.y); var definition = editor.findDefinition(offset); if (definition) { - _openOnRange(event.shiftKey ? "sub" : "main", definition, editor); + openOnRange(event.shiftKey ? "sub" : "main", definition, editor); } } } @@ -554,7 +554,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD fileSearcher: fileSearcher, fileSearchRenderer: fileSearcher.defaultRenderer, style:"width:800px", - _openOnRange: _openOnRange + openOnRange: openOnRange }); //TODO we should explicitly set focus to the previously active editor if the dialog has been canceled @@ -581,21 +581,21 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD editor.getTextView().setAction("Open declaration", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - _openOnRange(EDITOR_TARGET.main, definition, editor); + openOnRange(EDITOR_TARGET.main, definition, editor); } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ true, /*shift*/ false, /*alt*/ false), "Open declaration in new tab"); editor.getTextView().setAction("Open declaration in new tab", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - _openOnRange(EDITOR_TARGET.tab, definition, editor); + openOnRange(EDITOR_TARGET.tab, definition, editor); } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ false, /*shift*/ true, /*alt*/ false), "Open declaration in subeditor"); editor.getTextView().setAction("Open declaration in subeditor", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { - _openOnRange(EDITOR_TARGET.sub, definition, editor); + openOnRange(EDITOR_TARGET.sub, definition, editor); } }); }; @@ -765,11 +765,11 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD return { // private functions that are only exported to help with testing _loadEditor: loadEditor, - _openOnRange: _openOnRange, +// highlightSelection: highlightSelection, don't think we need this + openOnRange: openOnRange, initializeBreadcrumbs: initializeBreadcrumbs, navigationEventHandler: navigationEventHandler, -// highlightSelection: highlightSelection, don't think we need this popstateHandler: popstateHandler, toggleSidePanel: toggleSidePanel, navigate: navigate diff --git a/client/scripts/scripted/widgets/OpenOutlineDialog.js b/client/scripts/scripted/widgets/OpenOutlineDialog.js index b5b8be4d..b9649f90 100644 --- a/client/scripts/scripted/widgets/OpenOutlineDialog.js +++ b/client/scripts/scripted/widgets/OpenOutlineDialog.js @@ -13,7 +13,7 @@ * Andy Clement - initial API and implementation *******************************************************************************/ /*jslint browser:true*/ -/*global define orion window dojo dijit*/ +/*global define orion window dojo dijit scripted*/ define(['require', 'dojo', 'dijit', 'plugins/outline/esprimaOutlinerPlugin','dijit/Dialog', 'dijit/form/TextBox', 'scripted/widgets/_OrionDialogMixin', 'text!scripted/widgets/templates/OpenOutlineDialog.html'], diff --git a/client/scripts/scripted/widgets/SearchDialog.js b/client/scripts/scripted/widgets/SearchDialog.js index 71bdbc03..152d841c 100644 --- a/client/scripts/scripted/widgets/SearchDialog.js +++ b/client/scripts/scripted/widgets/SearchDialog.js @@ -73,7 +73,7 @@ var SearchDialog = dojo.declare("scripted.widgets.SearchDialog", [dijit.Dialog, /** @private */ navigateToResult: function (evt, result) { this.hide(); - this.openDeclaration(evt, { + this.openOnRange(evt, { path: result.path, range: [result.offset, result.offset+result.text.length] }, this.editor); From 6a4b0db7f5fcde24eaa62786144ff547c8e770bd Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 14:25:13 -0700 Subject: [PATCH 019/931] more tests. --- .../js-tests/navHIstory/navHistoryTests.js | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/client/scripts/js-tests/navHIstory/navHistoryTests.js b/client/scripts/js-tests/navHIstory/navHistoryTests.js index 3f7af76e..4c59be58 100644 --- a/client/scripts/js-tests/navHIstory/navHistoryTests.js +++ b/client/scripts/js-tests/navHIstory/navHistoryTests.js @@ -199,6 +199,35 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio assert.equal(historyMenu.children()[2].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#15,25"); }; + // test subeditor navigation applies to history + tests.testHistorycrumb5 = function() { + setup(); + var historyMenu = $("#history_menu"); + // history should be empty because no navigation happened + assert.equal(historyMenu.children().length, 0); + window.editor.setSelection(10, 20); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js", shiftKey:true }); + window.subeditors[0].setSelection(15, 25); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "baz.js", shiftKey:true }); + window.subeditors[0].setSelection(5, 10); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js", shiftKey:true }); + window.subeditors[0].setSelection(6, 7); + + // this one is not stored in history yet + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js" }); + window.subeditors[0].setSelection(6, 8); + historyMenu = $("#history_menu"); + + assert.equal(historyMenu.children().length, 3); + assert.equal(historyMenu.children()[0].children[0].innerHTML, "foo.js"); + assert.equal(historyMenu.children()[0].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "foo.js" + "#6,7"); + assert.equal(historyMenu.children()[1].children[0].innerHTML, "baz.js"); + assert.equal(historyMenu.children()[1].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "baz.js" + "#5,10"); + assert.equal(historyMenu.children()[2].children[0].innerHTML, "bar.js"); + assert.equal(historyMenu.children()[2].children[0].attributes[0].value, "/scripts/js-tests/scriptedClientServerTests.html?" + testResourcesRoot + "bar.js" + "#15,25"); + }; + tests.asyncTestGetContentsSubEditor = function() { setup(); setTimeout(function() { @@ -222,11 +251,71 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio }, 500); }; + tests.testEditorNavigation1 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + assert.deepEqual(window.editor.getSelection(), {start:20,end:30}); + }; + + tests.testEditorNavigation2 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#40,50" }); + assert.deepEqual(window.editor.getSelection(), {start:40,end:50}); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + assert.deepEqual(window.editor.getSelection(), {start:20,end:30}); + }; + + tests.testEditorNavigation3 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + assert.deepEqual(window.editor.getSelection(), {start:0,end:0}); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + assert.deepEqual(window.editor.getSelection(), {start:20,end:30}); + }; + + tests.testEditorNavigation4 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#NaN,NaN" }); + assert.deepEqual(window.editor.getSelection(), {start:0,end:0}); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + assert.deepEqual(window.editor.getSelection(), {start:20,end:30}); + }; + + tests.testSubeditorNavigation1 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:20,end:30}); + }; + + tests.testSubeditorNavigation2 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#40,50", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:40,end:50}); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:20,end:30}); + }; + + tests.testSubeditorNavigation3 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:0,end:0}); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:20,end:30}); + }; + + tests.testSubeditorNavigation4 = function() { + setup(); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#NaN,NaN", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:0,end:0}); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + assert.deepEqual(window.subeditors[0].getSelection(), {start:20,end:30}); + }; + // still to test // raw history object - // subeditor and state - // open on range + // click navigate + return tests; }); \ No newline at end of file From 885c0f602c86f6ab8b9962bb767f1f6bb89f2742 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 26 Oct 2012 14:44:57 -0700 Subject: [PATCH 020/931] A reimplementation of commonjs-resolver.js based on 'enhanced-resolve' library. --- server/jsdepend/commonjs-resolver-OLD.js | 102 +++++++++++++++++++++++ server/jsdepend/commonjs-resolver.js | 43 ++++------ server/jsdepend/debug.js | 2 +- server/jsdepend/package.json | 5 +- server/jsdepend/resolver-test.js | 27 ++++++ server/jsdepend/resolver.js | 8 +- 6 files changed, 156 insertions(+), 31 deletions(-) create mode 100644 server/jsdepend/commonjs-resolver-OLD.js diff --git a/server/jsdepend/commonjs-resolver-OLD.js b/server/jsdepend/commonjs-resolver-OLD.js new file mode 100644 index 00000000..ddc478b0 --- /dev/null +++ b/server/jsdepend/commonjs-resolver-OLD.js @@ -0,0 +1,102 @@ +/******************************************************************************* + * @license + * Copyright (c) 2012 VMware, Inc. All Rights Reserved. + * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE + * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE + * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT. + * You can obtain a current copy of the Eclipse Public License from + * http://www.opensource.org/licenses/eclipse-1.0.php + * + * Contributors: + * Kris De Volder - initial API and implementation + ******************************************************************************/ + +/*global resolve require define esprima console module process*/ +if (typeof define !== 'function') { + var define = require('amdefine')(module); +} +define(function(require, exports, module) { + +//////////////////////////////////////// +// commonjs-resolver +// +// Support for resolving commonjs references +///////////////////////////////////////// + +var parser = require("./parser"); +var treeMatcher = require('./tree-matcher'); +var getDirectory = require('./utils').getDirectory; +var pathResolve = require('./utils').pathResolve; + +function startsWith(str, pre) { + return str.lastIndexOf(pre, 0) === 0; +} +function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} + +var nodeNatives = require('./node-natives'); + +function configure(conf) { + + //Note: + // conf = the 'global' configuration for the api, provides file system type operations + // resolverConf = configuration information for the resolver, varies based on the context + // of where a reference came from. + + var andPat = treeMatcher.andPat; + var orPat = treeMatcher.orPat; + + var getContents = conf.getContents; + var orMap = require('./utils').orMap; + var listFiles = conf.listFiles; + var getScriptTags = require('./script-tag-finder').getScriptTags; + var getScriptCode = require('./script-tag-finder').getScriptCode; + var objectPat = treeMatcher.objectPat; + var successPat = treeMatcher.successPat; + var containsPat = treeMatcher.containsPat; + var successMatcher = treeMatcher.successMatcher; + var variablePat = treeMatcher.variablePat; + var arrayWithElementPat = treeMatcher.arrayWithElementPat; + var nodeModulesResolver = require('./node-modules-resolver').configure(conf); + +// function getNodeConfig(context, callback) { +// callback({}); +// //For now don't need any config, we only support resolving of './' references. +// //and that only require access to the location of the current file. +// } + + function resolver(context, dep, callback) { + if (startsWith(dep.name, './') || startsWith(dep.name, '../')) { + //can handle without determining some resolution base path etc. + //since it simply resolves relative to current file + var dir = getDirectory(context); + var searchFor = dep.name + '.js'; + dep.path = pathResolve(dir, searchFor); + callback(dep); + } else if (nodeNatives.isNativeNodeModule(dep.name)) { + dep.path = nodeNatives.MAGIC_PATH_PREFIX + dep.name; + callback(dep); + } else { + var nodeResolver = nodeModulesResolver.getResolver(context); + nodeResolver(dep.name, function (path) { + if (path) { + dep.path = path; + } + callback(dep); + }); + } + } + + //A 'resolver support' module provides a resolver for a particular kind of dependency. + return { + kind: 'commonjs', + resolver: resolver + }; + +} //end: function configure + +exports.configure = configure; + +///////////////////////////////////////////////////////////////////////// +}); //end amd define diff --git a/server/jsdepend/commonjs-resolver.js b/server/jsdepend/commonjs-resolver.js index ddc478b0..a8efeb43 100644 --- a/server/jsdepend/commonjs-resolver.js +++ b/server/jsdepend/commonjs-resolver.js @@ -36,29 +36,19 @@ function endsWith(str, suffix) { } var nodeNatives = require('./node-natives'); +var enhancedResolver = require('enhanced-resolve'); function configure(conf) { + var handle2file = conf.handle2file; + var file2handle = conf.file2handle; + //Note: // conf = the 'global' configuration for the api, provides file system type operations // resolverConf = configuration information for the resolver, varies based on the context // of where a reference came from. - var andPat = treeMatcher.andPat; - var orPat = treeMatcher.orPat; - - var getContents = conf.getContents; - var orMap = require('./utils').orMap; - var listFiles = conf.listFiles; - var getScriptTags = require('./script-tag-finder').getScriptTags; - var getScriptCode = require('./script-tag-finder').getScriptCode; - var objectPat = treeMatcher.objectPat; - var successPat = treeMatcher.successPat; - var containsPat = treeMatcher.containsPat; - var successMatcher = treeMatcher.successMatcher; - var variablePat = treeMatcher.variablePat; - var arrayWithElementPat = treeMatcher.arrayWithElementPat; - var nodeModulesResolver = require('./node-modules-resolver').configure(conf); + //var nodeModulesResolver = require('./node-modules-resolver').configure(conf); // function getNodeConfig(context, callback) { // callback({}); @@ -67,21 +57,20 @@ function configure(conf) { // } function resolver(context, dep, callback) { - if (startsWith(dep.name, './') || startsWith(dep.name, '../')) { - //can handle without determining some resolution base path etc. - //since it simply resolves relative to current file - var dir = getDirectory(context); - var searchFor = dep.name + '.js'; - dep.path = pathResolve(dir, searchFor); - callback(dep); - } else if (nodeNatives.isNativeNodeModule(dep.name)) { + if (nodeNatives.isNativeNodeModule(dep.name)) { dep.path = nodeNatives.MAGIC_PATH_PREFIX + dep.name; callback(dep); } else { - var nodeResolver = nodeModulesResolver.getResolver(context); - nodeResolver(dep.name, function (path) { - if (path) { - dep.path = path; + //Notes: + //1: The enhanced resolver is a node module so it uses 'real' file system paths. + //Therfore we must make sure to translate back and forth between our own internal file handles. + + //2: enhanced resolver expects a directory as the 'context' not a file. + enhancedResolver(getDirectory(handle2file(context)), dep.name, function (err, result) { + if (err) { + dep.error = err; + } else { + dep.path = file2handle(result); } callback(dep); }); diff --git a/server/jsdepend/debug.js b/server/jsdepend/debug.js index 78b253cd..9c84d653 100644 --- a/server/jsdepend/debug.js +++ b/server/jsdepend/debug.js @@ -18,7 +18,7 @@ //THIS CODE IS NOT PART OF scripted (its not loaded by any other module) //var testCase = require('./module-types-test.js').bigFile; -var testCase = require('./api-test.js').withParseErrors; +var testCase = require('./resolver-test.js').resolveNodeModulesWithProblem3; testCase({ equals: function (a, b) { diff --git a/server/jsdepend/package.json b/server/jsdepend/package.json index b0352a05..873a5de0 100644 --- a/server/jsdepend/package.json +++ b/server/jsdepend/package.json @@ -1,14 +1,15 @@ { "name": "jsdepend" -, "version": "0.0.1" +, "version": "0.0.3" , "main": "api.js" , "private": true , "dependencies": { "amdefine": "0.0.2" , "htmlparser": "1.7.6" + , "enhanced-resolve": "0.2.9" } , "scripts" : { "test": "./all-tests.sh" }, - "engine": "node ~> 0.6.x" + "engine": "node ~> 0.8.11" } diff --git a/server/jsdepend/resolver-test.js b/server/jsdepend/resolver-test.js index a89949cd..150d8c26 100644 --- a/server/jsdepend/resolver-test.js +++ b/server/jsdepend/resolver-test.js @@ -400,6 +400,33 @@ exports.resolveNodeModulesInParentDir = function(test) { ); }; +exports.resolveNodeModulesWithProblem3 = function(test) { + var api = makeApi('node-with-bad-data'); + var depNames = ['foo3']; + var expectedPaths = [ + undefined //won't be found because json is unparsable + ]; + + var deps = map(depNames, function (name) { + return { + name: name, + kind: 'commonjs' + }; + }); + mapk(deps, function (dep, k) { + api.resolve('subfolder/main.js', dep, k); + }, + function (resolveds) { + test.equals(resolveds.length, expectedPaths.length); + for (var i = 0; i < expectedPaths.length; i++) { + test.equals(expectedPaths[i], resolveds[i].path); + } + //console.log(resolveds); + test.done(); + } + ); +}; + exports.resolveNodeModulesWithProblems = function(test) { var api = makeApi('node-with-bad-data'); var depNames = ['foo', 'foo2', 'foo3', 'foo4', 'bar', 'zor', 'booger']; diff --git a/server/jsdepend/resolver.js b/server/jsdepend/resolver.js index 3d4402bc..3f02a7d0 100644 --- a/server/jsdepend/resolver.js +++ b/server/jsdepend/resolver.js @@ -27,6 +27,8 @@ define(function(require, exports, module) { function configure(conf) { var sloppy = conf.sloppy; + var useOldResolver = conf.useOldResolver; + if (typeof(sloppy)==='undefined') { console.trace('WARNING: sloppy mode is undefined. Assuming it will be disabled'); } @@ -188,10 +190,14 @@ function configure(conf) { } } + var mCommonJsResolverNew = require('./commonjs-resolver'); + var mCommonJsResolverOld = require('./commonjs-resolver-OLD'); + var mCommonJsResolver = useOldResolver ? mCommonJsResolverOld : mCommonJsResolverNew; + var resolvers = { 'list': listResolver, 'AMD': require('./amd-resolver').configure(conf).resolver, - 'commonjs': require('./commonjs-resolver').configure(conf).resolver + 'commonjs': mCommonjsResolver.configure(conf).resolver }; if (sloppy) { resolvers.AMD = compose(resolvers.AMD, searchByNameResolver); From bbdd91da97ba8347cc2851e545d029b43e2dcff5 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 26 Oct 2012 14:46:20 -0700 Subject: [PATCH 021/931] Add clean copy of enhanced-resolve version 0.2.9 --- .../node_modules/enhanced-resolve/.npmignore | 1 + .../node_modules/enhanced-resolve/.travis.yml | 4 + .../node_modules/enhanced-resolve/README.md | 18 + .../lib/createThrottledFunction.js | 91 ++++ .../enhanced-resolve/lib/resolve.js | 451 ++++++++++++++++++ .../enhanced-resolve/package.json | 34 ++ .../enhanced-resolve/test/fixtures/a.js | 3 + .../enhanced-resolve/test/fixtures/abc.txt | 1 + .../enhanced-resolve/test/fixtures/b.js | 3 + .../enhanced-resolve/test/fixtures/c.js | 4 + .../enhanced-resolve/test/fixtures/complex.js | 13 + .../test/fixtures/lib/complex1.js | 1 + .../enhanced-resolve/test/fixtures/main1.js | 10 + .../enhanced-resolve/test/fixtures/main2.js | 12 + .../enhanced-resolve/test/fixtures/main3.js | 4 + .../fixtures/node_modules/complexm/step1.js | 1 + .../fixtures/node_modules/complexm/step2.js | 1 + .../node_modules/complexm/web_modules/m1/a.js | 1 + .../complexm/web_modules/m1/index.js | 1 + .../test/fixtures/node_modules/m1/a.js | 3 + .../test/fixtures/node_modules/m1/b.js | 3 + .../test/fixtures/node_modules/m2-loader/b.js | 3 + .../test/fixtures/node_modules/m2/b.js | 1 + .../enhanced-resolve/test/resolve.js | 74 +++ .../enhanced-resolve/test/simple.js | 30 ++ 25 files changed, 768 insertions(+) create mode 100644 server/jsdepend/node_modules/enhanced-resolve/.npmignore create mode 100644 server/jsdepend/node_modules/enhanced-resolve/.travis.yml create mode 100644 server/jsdepend/node_modules/enhanced-resolve/README.md create mode 100644 server/jsdepend/node_modules/enhanced-resolve/lib/createThrottledFunction.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/package.json create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/a.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/abc.txt create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/b.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/c.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/complex.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/lib/complex1.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main1.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main2.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main3.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step1.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step2.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/a.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/index.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/a.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/b.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2-loader/b.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2/b.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/resolve.js create mode 100644 server/jsdepend/node_modules/enhanced-resolve/test/simple.js diff --git a/server/jsdepend/node_modules/enhanced-resolve/.npmignore b/server/jsdepend/node_modules/enhanced-resolve/.npmignore new file mode 100644 index 00000000..4dcec4c8 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/.npmignore @@ -0,0 +1 @@ +/node_modules diff --git a/server/jsdepend/node_modules/enhanced-resolve/.travis.yml b/server/jsdepend/node_modules/enhanced-resolve/.travis.yml new file mode 100644 index 00000000..90afc1ed --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 + - 0.8 \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/README.md b/server/jsdepend/node_modules/enhanced-resolve/README.md new file mode 100644 index 00000000..c1c50586 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/README.md @@ -0,0 +1,18 @@ +# enhanced-resolve + +Offers a async require.resolve function. It's highly configurable. + +[![Build Status](https://secure.travis-ci.org/webpack/enhanced-resolve.png?branch=master)](http://travis-ci.org/webpack/enhanced-resolve) + +More documentation coming soon... + +``` javascript +var resolve = require("enhanced-resolve"); + +resolve(string context, string identifier, object options, function callback(err, result)) +resolve.sync(string context, string identifier, object options) +resolve.context(string context, string identifier, object options, function callback(err, result)) +resolve.context.sync(string context, string identifier, object options) +``` + +*It is used in [webpack](/webpack/webpack)* \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/lib/createThrottledFunction.js b/server/jsdepend/node_modules/enhanced-resolve/lib/createThrottledFunction.js new file mode 100644 index 00000000..cdf5c610 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/lib/createThrottledFunction.js @@ -0,0 +1,91 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +module.exports = function createThrottledFunction(fn, MAX_DURATION, cache) { + MAX_DURATION = MAX_DURATION || 2000; + var RESOLUTION = Math.floor(Math.min(MAX_DURATION / 20, 300)); + cache = cache || {}; + var lastTime = new Date(); + var currentTime = lastTime; + var trottled = false; + + function tick() { + currentTime = new Date(); + + trottled = true; + setTimeout(unthrottle, RESOLUTION); + } + + function unthrottle() { + trottled = false; + } + + return function throttledFunction(identifier) { + var args = Array.prototype.slice.call(arguments, 0); + var callback = args.pop(); + + if(!trottled) { + tick(); + + // clear all if old + if(currentTime - lastTime > MAX_DURATION) { + for(var name in cache) + delete cache[name]; + } + } + + var cacheEntry = cache[identifier]; + + if(cacheEntry) { + if(cacheEntry.listeners) { + cacheEntry.listeners.push(callback); + return cacheEntry.retVal; + } else if(currentTime - cacheEntry.time <= MAX_DURATION) { + callback(cacheEntry.err, cacheEntry.result); + return cacheEntry.retVal; + } + } + + var listeners = [callback]; + cache[identifier] = cacheEntry = { + listeners: listeners + }; + + // do the request + lastTime = currentTime; + var requestTime = currentTime; + args.push(function onThrottledFunctionCallback(err, result) { + cacheEntry.time = requestTime; + cacheEntry.err = err; + cacheEntry.result = result; + delete cacheEntry.listeners; + listeners.forEach(function forEachFn(listener) { + listener(err, result); + }); + }); + var retVal = fn.apply(null, args); + cacheEntry.retVal = retVal; + return retVal; + } +} + +module.exports.sync = function createThrottledFunctionSync(fn, MAX_DURATION) { + var err, result; + var throttledFunction = createThrottledFunction(function() { + var retVal, args = Array.prototype.slice.call(arguments, 0); + var callback = args.pop(); + try { + retVal = fn.apply(null, args); + } catch(e) { callback(e); } + callback(); + return retVal; + }, MAX_DURATION); + return function() { + var args = Array.prototype.slice.call(arguments, 0); + args.push(nop); + return throttledFunction.apply(null, args); + } +} + +function nop(err) { if(err) throw err; } diff --git a/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js b/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js new file mode 100644 index 00000000..7cdeda3c --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js @@ -0,0 +1,451 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +var path = require("path"); +var fs = require("fs"); +var createThrottledFunction = require("./createThrottledFunction"); +var statCache = {}, readFileCache = {}; +var statAsync = createThrottledFunction(fs.stat, 2000, Object.create(statCache)); +var statSync = createThrottledFunction(function(pathname, callback) { + try { callback(null, fs.statSync(pathname)); } catch(e) { callback(e) } +}, 4000, statCache); +var readFileAsync = createThrottledFunction(fs.readFile, 2000, Object.create(readFileCache)); +var readFileSync = createThrottledFunction(function(pathname, enc, callback) { + try { callback(null, fs.readFileSync(pathname, enc)); } catch(e) { callback(e) } +}, 4000, readFileCache); + +// http://nodejs.org/docs/v0.4.8/api/all.html#all_Together... + + +function resolve(context, identifier, options, type, sync, callback) { + function finalResult(err, absoluteFilename) { + if(err) { + callback(new Error("Module \"" + identifier + "\" not found in context \"" + + context + "\"\n " + err)); + return; + } + callback(null, absoluteFilename); + } + var identArray = split(identifier); + var contextArray = split(context); + while(options.alias[identArray[0]]) { + var old = identArray[0]; + identArray[0] = options.alias[identArray[0]]; + identArray = split(path.join.apply(path, identArray)); + if(identArray[0] === old) + break; + } + if(identArray[0] === "." || identArray[0] === ".." || identArray[0] === "" || identArray[0].match(/^[A-Z]:$/i)) { + var pathname = identArray[0][0] === "." ? join(contextArray, identArray) : join(identArray, []); + if(type === "context") { + (sync?statSync:statAsync)(pathname, function(err, stat) { + if(err) { + finalResult(err); + return; + } + if(!stat.isDirectory()) { + finalResult(new Error("Context \"" + identifier + "\" in not a directory")); + return; + } + callback(null, pathname); + }); + } else { + loadAsFileOrDirectory(pathname, options, type, sync, finalResult); + } + } else { + loadNodeModules(contextArray, identArray, options, type, sync, finalResult); + } +} + +function doResolve(context, identifier, options, type, sync, callback) { + var identifiers = identifier.replace(/^!|!$/g, "").replace(/!!/g, "!").split(/!/g); + var resource = identifiers.pop(); + if(identifier.indexOf("!") === -1) { + // We need the resource first to check if loaders apply. + // We have to do it serial. + resolve(context, resource, options, type, sync, function(err, resource) { + if(err) return callback(err); + for(var i = 0; i < options.loaders.length; i++) { + var line = options.loaders[i]; + var test = line.test; + if(typeof test == "string") test = new RegExp(test); + if(test.test(resource)) { + Array.prototype.push.apply(identifiers, line.loader.split(/!/g)); + break; + } + } + if(identifiers.length == 0) return onResolvedBoth([], resource); + + resolveLoaders(context, identifiers, options, sync, function(err, loaders) { + if(err) return callback(err); + onResolvedBoth(loaders, resource); + }); + }); + } else { + // Loaders are specified. Do it parallel. + var fastExit = false; + var count = 0; + var loaders, resource; + + resolve(context, resource, options, type, sync, function(err, res) { + if(err) { + fastExit = true; + return callback(err); + } + resource = res; + if(count++) return onResolvedBoth(loaders, resource); + }); + resolveLoaders(context, identifiers, options, sync, function(err, theLoaders) { + if(err) { + fastExit = true; + return callback(err); + } + loaders = theLoaders; + if(count++) return onResolvedBoth(loaders, resource); + }); + } + function onResolvedBoth(loaders, resource) { + loaders.push(resource); + var intermediateResult = loaders.join("!"); + var postprocessors = options.postprocess[type].slice(0); + postprocessors.push(function(result) { + callback(null, result); + }); + (function next(err, result) { + if(err) + return callback(new Error("File \"" + intermediateResult + "\" is blocked by postprocessors: " + err)); + var postprocessor = postprocessors.shift(); + if(typeof postprocessor == "string") postprocessor = require(postprocessor); + postprocessor(result, next); + })(null, intermediateResult); + } +} + +function resolveLoaders(context, identifiers, options, sync, callback) { + var errors = []; + var count = identifiers.length; + function endOne() { + count--; + if(count === 0) { + if(errors.length > 0) { + callback(new Error(errors.join("\n"))); + return; + } + callback(null, identifiers); + } + } + if(count == 0) return endOne(count++); + identifiers.forEach(function(ident, index) { + resolve(context, ident, options, "loader", sync, function(err, filename) { + if(err) { + errors.push(err); + } else { + identifiers[index] = filename; + } + endOne() + }); + }); +} + +/** + * sets not defined options to node.js defaults + */ +function setupDefaultOptions(options) { + if(!options) + options = {}; + if(!options.extensions) + options.extensions = ["", ".js"]; + if(!options.loaders) + options.loaders = []; + if(!options.postfixes) + options.postfixes = [""]; + if(!options.loaderExtensions) + options.loaderExtensions = [".node-loader.js", ".loader.js", "", ".js"]; + if(!options.loaderPostfixes) + options.loaderPostfixes = ["-node-loader", "-loader", ""]; + if(!options.paths) + options.paths = []; + if(!options.modulesDirectorys) + options.modulesDirectorys = ["node_modules"]; + if(!options.alias) + options.alias = {}; + if(!options.postprocess) + options.postprocess = {}; + if(!options.postprocess.normal) + options.postprocess.normal = []; + if(!options.postprocess.context) + options.postprocess.context = []; + return options; +} + +function createSyncCallback() { + var err, result; + function fn(_err, _result) { + err = _err; + result = _result; + } + fn.get = function() { + if(err) throw err; + return result; + } + return fn; +} + +/** + * context: absolute filename of current file + * identifier: module to find + * options: + * paths: array of lookup paths + * callback: function(err, absoluteFilename) + */ +module.exports = function(context, identifier, options, callback) { + if(!callback) { + callback = options; + options = {}; + } + options = setupDefaultOptions(options); + return doResolve(context, identifier, options, "normal", false, callback); +} +module.exports.sync = function(context, identifier, options) { + if(!options) options = {}; + options = setupDefaultOptions(options); + var callback = createSyncCallback(); + doResolve(context, identifier, options, "normal", true, callback); + return callback.get(); +} +module.exports.setupDefaultOptions = setupDefaultOptions; + +module.exports.context = function(context, identifier, options, callback) { + if(!callback) { + callback = options; + options = {}; + } + options = setupDefaultOptions(options); + return doResolve(context, identifier, options, "context", false, callback); +} +module.exports.context.sync = function(context, identifier, options) { + if(!options) options = {}; + options = setupDefaultOptions(options); + var callback = createSyncCallback(); + doResolve(context, identifier, options, "context", true, callback); + return callback.get(); +} + +/** + * callback: function(err, absoluteFilenamesArray) + */ +module.exports.loaders = function(context, identifier, options, callback) { + if(!callback) { + callback = options; + options = {}; + } + options = setupDefaultOptions(options); + var identifiers = identifier.replace(/^!|!$/g, "").replace(/!!/g, "!").split(/!/g); + if(identifiers.length == 1 && identifiers[0] == "") return callback(null, []); + return resolveLoaders(context, identifiers, options, false, callback); +} + + +function split(a) { + return a.split(/[\/\\]/g); +} + +function join(a, b) { + var c = []; + a.forEach(function(x) { c.push(x) }); + b.forEach(function(x) { c.push(x) }); + if(c[0] === "") // fix *nix paths + c[0] = "/"; + return path.join.apply(path, c); +} + +function loadAsFile(filename, options, type, sync, callback) { + var extensions = type === "loader" ? options.loaderExtensions : options.extensions; + var tries = extensions.map(function(ext) { + return filename + ext; + }); + var count = tries.length; + var results = tries.slice(0); + tries.forEach(function forEachTryFn(test, idx) { + (sync?statSync:statAsync)(test, function loadAsFileTryCallback(err, stat) { + results[idx] = (err || !stat || !stat.isFile()) ? null : test; + count--; + if(count === 0) { + for(var i = 0; i < tries.length; i++) { + if(results[i]) return callback(null, tries[i]); + } + return callback(new Error("Non of this files exists: " + tries.join(", "))); + } + }); + }); +} + +function loadAsDirectory(dirname, options, type, sync, callback) { + (sync?statSync:statAsync)(dirname, function(err, stats) { + if(err || !stats || !stats.isDirectory()) { + return callback(new Error(dirname + " is not a directory")); + } + var packageJsonFile = join(split(dirname), ["package.json"]); + (sync?statSync:statAsync)(packageJsonFile, function(err, stats) { + var mainModule = "index"; + if(!err && stats.isFile()) { + (sync?readFileSync:readFileAsync)(packageJsonFile, "utf-8", function(err, content) { + if(err) { + callback(err); + return; + } + content = JSON.parse(content); + if(content.webpackLoader && type === "loader") + mainModule = content.webpackLoader; + else if(content.webpack) + mainModule = content.webpack; + else if(content.browserify) + mainModule = content.browserify; + else if(content.main) + mainModule = content.main; + loadAsFile(join(split(dirname), [mainModule]), options, type, sync, function(err, absoluteFilename) { + if(!err) return callback(null, absoluteFilename); + loadAsFile(join(split(dirname), [mainModule, "index"]), options, type, sync, function(err2, absoluteFilename) { + if(err2) return callback(err); + return callback(null, absoluteFilename); + }) + }); + }); + } else + loadAsFile(join(split(dirname), [mainModule]), options, type, sync, callback); + }); + }); +} + +function loadAsFileOrDirectory(pathname, options, type, sync, callback) { + var result = null; + var counter = 0; + var error = null; + var fastExit = false; + loadAsFile(pathname, options, type, sync, function loadAsFileOrDirectoryFileResultCallback(err, absoluteFilename) { + if(err) + error = err; + else { + fastExit = true; + return callback(null, absoluteFilename); + } + if(counter++) bothDone(); + }); + loadAsDirectory(pathname, options, type, sync, function loadAsFileOrDirectoryDirectoryResultCallback(err, absoluteFilename) { + if(err) { + if(!error) error = err; + } else { + result = absoluteFilename; + } + if(counter++) bothDone(); + }); + function bothDone() { + if(fastExit) return; + if(result) + callback(null, result); + else + callback(error); + } +} + +function loadNodeModules(context, identifier, options, type, sync, callback) { + var moduleName = identifier.shift(); + var postfixes = type === "loader" ? options.loaderPostfixes : options.postfixes; + var paths = nodeModulesPaths(context, options); + (sync?iterateSync:iterateAsync)(options.paths, function(path, idx, next) { + usePath(path, next); + }, function() { + (sync?iterateSync:iterateAsync)(paths, function(path, idx, next) { + (sync?statSync:statAsync)(path, function(err, stat) { + if(err || !stat || !stat.isDirectory()) + return next(); + usePath(path, next); + }); + }, function() { + callback(new Error("non in any path of paths")); + }); + }); + function usePath(path, next) { + var dirs = []; + postfixes.forEach(function(postfix) { + dirs.push(join(split(path), [moduleName+postfix])); + }); + var count = dirs.length; + var results = dirs.slice(0); + var fastExit = false; + dirs.forEach(function(dir, idx) { + var pathname = join(split(dir), identifier); + if(type === "context") { + (sync?statSync:statAsync)(pathname, function(err, stat) { + results[idx] = (err || !stat.isDirectory()) ? null : pathname; + endOne(idx); + }); + } else { + loadAsFileOrDirectory(pathname, options, type, sync, function loadAsFileOrDirectoryCallback(err, absoluteFilename) { + results[idx] = err ? null : absoluteFilename; + endOne(idx); + }); + } + }); + function endOne(idx) { + if(fastExit) return; + count--; + if(count === 0) { + for(var i = 0; i < results.length; i++) { + if(results[i]) + return callback(null, results[i]); + } + next(); + } else if(results[idx]) { + for(var i = 0; i < idx; i++) { + if(results[i]) + return; + } + fastExit = true; + return callback(null, results[idx]); + } + } + } +} + +function nodeModulesPaths(context, options) { + var parts = context; + var root = 0; + options.modulesDirectorys.forEach(function(dir) { + var index = parts.indexOf(dir)-1; + if(index >= 0 && index < root) + root = index; + }); + var dirs = []; + for(var i = parts.length; i > root; i--) { + if(options.modulesDirectorys.indexOf(parts[i-1]) >= 0) + continue; + var part = parts.slice(0, i); + options.modulesDirectorys.forEach(function(dir) { + dirs.push(join(part, [dir])); + }); + } + return dirs; +} + +function iterateAsync(array, fn, cb) { + var i = 0; + (function next() { + var item = array[i++]; + if(!item) return cb(); + return fn(item, i-1, next); + })(); +} + +function iterateSync(array, fn, cb) { + var cond = true; + for(var i = 0; i < array.length && cond; i++) { + cond = false; + fn(array[i], i, next); + } + if(cond) cb(); + function next() { + cond = true; + } +} \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/package.json b/server/jsdepend/node_modules/enhanced-resolve/package.json new file mode 100644 index 00000000..bc95326e --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/package.json @@ -0,0 +1,34 @@ +{ + "name": "enhanced-resolve", + "version": "0.2.9", + "author": { + "name": "Tobias Koppers @sokra" + }, + "description": "Offers a async require.resolve function. It's highly configurable.", + "dependencies": {}, + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + ], + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "engines": { + "node": ">=0.6" + }, + "main": "lib/resolve.js", + "homepage": "http://github.com/webpack/enhanced-resolve", + "scripts": { + "test": "node node_modules/mocha/bin/_mocha --reporter spec" + }, + "license": "MIT", + "readme": "# enhanced-resolve\r\n\r\nOffers a async require.resolve function. It's highly configurable.\r\n\r\n[![Build Status](https://secure.travis-ci.org/webpack/enhanced-resolve.png?branch=master)](http://travis-ci.org/webpack/enhanced-resolve)\r\n\r\nMore documentation coming soon...\r\n\r\n``` javascript\r\nvar resolve = require(\"enhanced-resolve\");\r\n\r\nresolve(string context, string identifier, object options, function callback(err, result))\r\nresolve.sync(string context, string identifier, object options)\r\nresolve.context(string context, string identifier, object options, function callback(err, result))\r\nresolve.context.sync(string context, string identifier, object options)\r\n```\r\n\r\n*It is used in [webpack](/webpack/webpack)*", + "_id": "enhanced-resolve@0.2.9", + "dist": { + "shasum": "0b3522e103964b94b6b89652900ddd3ff5dbe531" + }, + "_from": "enhanced-resolve@0.2.9" +} diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/a.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/a.js new file mode 100644 index 00000000..c0fa989e --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/a.js @@ -0,0 +1,3 @@ +module.exports = function a() { + return "This is a"; +}; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/abc.txt b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/abc.txt new file mode 100644 index 00000000..f2ba8f84 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/abc.txt @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/b.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/b.js new file mode 100644 index 00000000..eef08b54 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/b.js @@ -0,0 +1,3 @@ +module.exports = function b() { + return "This is b"; +}; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/c.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/c.js new file mode 100644 index 00000000..6de64f47 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/c.js @@ -0,0 +1,4 @@ +module.exports = function b() { + require("./a"); + return "This is c"; +}; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/complex.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/complex.js new file mode 100644 index 00000000..f4c90f88 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/complex.js @@ -0,0 +1,13 @@ +var complex1 = require("./lib/complex1"); +require.ensure(["./lib/complex1", "complexm/step2"], function(require) { + require("./lib/complex1"); + var a = function() {} + require.ensure(["complexm/step1"], function(require) { + require("./lib/complex1"); + var s1 = require("complexm/step1"); + var s2 = require("complexm/step2"); + console.log(s1); + console.log(s2); + }); +}); +console.log(complex1); diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/lib/complex1.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/lib/complex1.js new file mode 100644 index 00000000..1f128fa2 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/lib/complex1.js @@ -0,0 +1 @@ +module.exports = "lib complex1"; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main1.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main1.js new file mode 100644 index 00000000..ca5f3e0d --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main1.js @@ -0,0 +1,10 @@ +var a = require("./a"); +if(x) { + for(var i = 0; i < 100; i++) { + while(true) + require("./b"); + do { + i++; + } while(require("m1/a")()); + } +} \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main2.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main2.js new file mode 100644 index 00000000..d0fef401 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main2.js @@ -0,0 +1,12 @@ +var a = require("./a"); +with(x) { + switch(a) { + case 1: + require("./b"); + default: + require.ensure(["m1/a"], function() { + var a = require("m1/a"), + b = require("m1/b"); + }); + } +} \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main3.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main3.js new file mode 100644 index 00000000..8455af56 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/main3.js @@ -0,0 +1,4 @@ +var a = require("./a"); +require.ensure([], function(require) { + require("./c.js"); +}); \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step1.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step1.js new file mode 100644 index 00000000..30dfd778 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step1.js @@ -0,0 +1 @@ +module.exports = require("m1/a") + require("m1"); \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step2.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step2.js new file mode 100644 index 00000000..10bad86c --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/step2.js @@ -0,0 +1 @@ +module.exports = "Step2"; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/a.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/a.js new file mode 100644 index 00000000..16fd73e6 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/a.js @@ -0,0 +1 @@ +module.exports = "the correct a.js"; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/index.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/index.js new file mode 100644 index 00000000..ef32ecd0 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/complexm/web_modules/m1/index.js @@ -0,0 +1 @@ +module.exports = " :) " + require("m2/b.js"); \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/a.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/a.js new file mode 100644 index 00000000..1266992f --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/a.js @@ -0,0 +1,3 @@ +module.exports = function a() { + return "This is m1/a"; +}; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/b.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/b.js new file mode 100644 index 00000000..ed8ff756 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m1/b.js @@ -0,0 +1,3 @@ +module.exports = function a() { + return "This is m1/b"; +}; \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2-loader/b.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2-loader/b.js new file mode 100644 index 00000000..1bd7c33a --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2-loader/b.js @@ -0,0 +1,3 @@ +module.exports = function() { + "module.exports = 'This is m2-loader/b';"; +} diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2/b.js b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2/b.js new file mode 100644 index 00000000..c9d20825 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/fixtures/node_modules/m2/b.js @@ -0,0 +1 @@ +module.exports = "This is m2/b"; diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/resolve.js b/server/jsdepend/node_modules/enhanced-resolve/test/resolve.js new file mode 100644 index 00000000..26a2572a --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/resolve.js @@ -0,0 +1,74 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +var should = require("should"); +var path = require("path"); +var resolve = require("../lib/resolve"); + +var fixtures = path.join(__dirname, "fixtures"); +function testResolve(name, context, moduleName, result) { + describe(name, function() { + it("should resolve correctly", function(done) { + resolve(context, moduleName, {}, function(err, filename) { + if(err) done(err); + should.exist(filename); + filename.should.equal(result); + done(); + }); + }); + }); +} +function testResolveContext(name, context, moduleName, result) { + describe(name, function() { + it("should resolve correctly", function(done) { + resolve.context(context, moduleName, {}, function(err, filename) { + if(err) done(err); + should.exist(filename) + filename.should.equal(result); + done(); + }); + }); + }); +} +describe("resolve", function() { + testResolve("file with .js", + fixtures, "./main1.js", path.join(fixtures, "main1.js")); + testResolve("file without extension", + fixtures, "./main1", path.join(fixtures, "main1.js")); + testResolve("another file with .js", + fixtures, "./a.js", path.join(fixtures, "a.js")); + testResolve("another file without extension", + fixtures, "./a", path.join(fixtures, "a.js")); + testResolve("file in module with .js", + fixtures, "m1/a.js", path.join(fixtures, "node_modules", "m1", "a.js")); + testResolve("file in module without extension", + fixtures, "m1/a", path.join(fixtures, "node_modules", "m1", "a.js")); + testResolve("another file in module without extension", + fixtures, "complexm/step1", path.join(fixtures, "node_modules", "complexm", "step1.js")); + testResolve("from submodule to file in sibling module", + path.join(fixtures, "node_modules", "complexm"), "m2/b.js", path.join(fixtures, "node_modules", "m2", "b.js")); + testResolve("from submodule to file in sibling of parent module", + path.join(fixtures, "node_modules", "complexm", "web_modules", "m1"), "m2/b.js", path.join(fixtures, "node_modules", "m2", "b.js")); + + testResolve("loader", + fixtures, "m1/a!./main1.js", path.join(fixtures, "node_modules", "m1", "a.js") + "!" + path.join(fixtures, "main1.js")); + testResolve("loader with prefix", + fixtures, "m2/b!./main1.js", path.join(fixtures, "node_modules", "m2-loader", "b.js") + "!" + path.join(fixtures, "main1.js")); + testResolve("multiple loaders", + fixtures, "m1/a!m1/b!m2/b!./main1.js", path.join(fixtures, "node_modules", "m1", "a.js") + "!" + + path.join(fixtures, "node_modules", "m1", "b.js") + "!" + + path.join(fixtures, "node_modules", "m2-loader", "b.js") + "!" + + path.join(fixtures, "main1.js")); + + testResolveContext("context for fixtures", + fixtures, "./", fixtures); + testResolveContext("context for fixtures/lib", + fixtures, "./lib", path.join(fixtures, "lib")); + testResolveContext("context with loader", + fixtures, "m1/a!./", path.join(fixtures, "node_modules", "m1", "a.js") + "!" + fixtures); + testResolveContext("context with loaders in parent directory", + fixtures, "m1/a!m2/b.js!../", path.join(fixtures, "node_modules", "m1", "a.js") + "!" + + path.join(fixtures, "node_modules", "m2-loader", "b.js") + "!" + + path.join(fixtures, "..")); +}); \ No newline at end of file diff --git a/server/jsdepend/node_modules/enhanced-resolve/test/simple.js b/server/jsdepend/node_modules/enhanced-resolve/test/simple.js new file mode 100644 index 00000000..a01dd7f5 --- /dev/null +++ b/server/jsdepend/node_modules/enhanced-resolve/test/simple.js @@ -0,0 +1,30 @@ +var resolve = require("../lib/resolve"); +var should = require("should"); +var path = require("path"); + +describe("simple", function() { + var pathsToIt = [ + [__dirname, "../lib/resolve", "direct"], + [__dirname, "../", "as directory"], + [path.join(__dirname, "..", ".."), "./enhanced-resolve", "as module"], + [path.join(__dirname, "..", ".."), "./enhanced-resolve/lib/resolve", "in module"] + ]; + pathsToIt.forEach(function(pathToIt) { + it("should resolve itself " + pathToIt[2], function(done) { + resolve(pathToIt[0], pathToIt[1], function(err, filename) { + if(err) return done(err); + should.exist(filename); + filename.should.be.a("string"); + filename.should.be.eql(path.join(__dirname, "..", "lib", "resolve.js")); + done(); + }); + }); + it("should resolve itself sync " + pathToIt[2], function() { + var filename = resolve.sync(pathToIt[0], pathToIt[1]); + should.exist(filename); + filename.should.be.a("string"); + filename.should.be.eql(path.join(__dirname, "..", "lib", "resolve.js")); + }); + }); + +}); \ No newline at end of file From 36f3baea1b7609b1b8e038951529275dd6493be2 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 26 Oct 2012 14:49:48 -0700 Subject: [PATCH 022/931] Revert some breaking changes in resolver.js --- server/jsdepend/resolver.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/server/jsdepend/resolver.js b/server/jsdepend/resolver.js index 3f02a7d0..8a02c2a0 100644 --- a/server/jsdepend/resolver.js +++ b/server/jsdepend/resolver.js @@ -27,7 +27,6 @@ define(function(require, exports, module) { function configure(conf) { var sloppy = conf.sloppy; - var useOldResolver = conf.useOldResolver; if (typeof(sloppy)==='undefined') { console.trace('WARNING: sloppy mode is undefined. Assuming it will be disabled'); @@ -190,14 +189,10 @@ function configure(conf) { } } - var mCommonJsResolverNew = require('./commonjs-resolver'); - var mCommonJsResolverOld = require('./commonjs-resolver-OLD'); - var mCommonJsResolver = useOldResolver ? mCommonJsResolverOld : mCommonJsResolverNew; - var resolvers = { 'list': listResolver, 'AMD': require('./amd-resolver').configure(conf).resolver, - 'commonjs': mCommonjsResolver.configure(conf).resolver + 'commonjs': require('./commonjs-resolver').configure(conf).resolver }; if (sloppy) { resolvers.AMD = compose(resolvers.AMD, searchByNameResolver); From be7e8cd2c3268a767459f3026fa20f90193f308f Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 26 Oct 2012 14:58:02 -0700 Subject: [PATCH 023/931] Small patch to enhanced-resolve library to fix a bug causing a test failure in scripted. --- .../jsdepend/node_modules/enhanced-resolve/lib/resolve.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js b/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js index 7cdeda3c..5952867c 100644 --- a/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js +++ b/server/jsdepend/node_modules/enhanced-resolve/lib/resolve.js @@ -295,7 +295,12 @@ function loadAsDirectory(dirname, options, type, sync, callback) { callback(err); return; } - content = JSON.parse(content); + try { + content = JSON.parse(content); + } catch (jsonErr) { + callback(jsonErr); + return; + } if(content.webpackLoader && type === "loader") mainModule = content.webpackLoader; else if(content.webpack) From cc11743d723b0edec2a9827d40c2b4fe5fb098d3 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Fri, 26 Oct 2012 15:09:38 -0700 Subject: [PATCH 024/931] re-add something Andrew commented out. it is required for the help panel to function --- client/scripts/setup.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/scripts/setup.js b/client/scripts/setup.js index 9ab65e1c..b63a420e 100644 --- a/client/scripts/setup.js +++ b/client/scripts/setup.js @@ -97,7 +97,8 @@ requirejs.config({ sockjs:'lib/sockjs-592774a-0.3.1.min', fileapi: 'scripted/fileapi', 'esprima/esprima' : 'lib/esprima/esprima', - 'doctrine/doctrine' : 'lib/doctrine/doctrine' + 'doctrine/doctrine' : 'lib/doctrine/doctrine', + jshint: 'lib/jshint-r12-80277ef' } }); @@ -300,12 +301,11 @@ require(["scripted/editor/scriptedEditor", "scripted/navigator/explorer-table", } }); - // TODO FIXADE I think we can delete -// $.views.helpers({ -// isMac: function(){ -// return (window.navigator.platform.indexOf("Mac") !== -1); -// } -// }); + $.views.helpers({ + isMac: function(){ + return (window.navigator.platform.indexOf("Mac") !== -1); + } + }); var command_file = "resources/_command.tmpl.html"; // use a copy so we can sort From a1a0bc503f02fa0a10d81d4ee2d709aa3a3de2eb Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Fri, 26 Oct 2012 15:09:52 -0700 Subject: [PATCH 025/931] jshint --- client/scripts/lib/jshint-r12-80277ef.js | 4832 ++++++++++++++++++++++ 1 file changed, 4832 insertions(+) create mode 100644 client/scripts/lib/jshint-r12-80277ef.js diff --git a/client/scripts/lib/jshint-r12-80277ef.js b/client/scripts/lib/jshint-r12-80277ef.js new file mode 100644 index 00000000..53724f5c --- /dev/null +++ b/client/scripts/lib/jshint-r12-80277ef.js @@ -0,0 +1,4832 @@ +/*! + * JSHint, by JSHint Community. + * + * Licensed under the same slightly modified MIT license that JSLint is. + * It stops evil-doers everywhere. + * + * JSHint is a derivative work of JSLint: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * JSHint was forked from the 2010-12-16 edition of JSLint. + * + */ + +/* + JSHINT is a global function. It takes two parameters. + + var myResult = JSHINT(source, option); + + The first parameter is either a string or an array of strings. If it is a + string, it will be split on '\n' or '\r'. If it is an array of strings, it + is assumed that each string represents one line. The source can be a + JavaScript text or a JSON text. + + The second parameter is an optional object of options which control the + operation of JSHINT. Most of the options are booleans: They are all + optional and have a default value of false. One of the options, predef, + can be an array of names, which will be used to declare global variables, + or an object whose keys are used as global names, with a boolean value + that determines if they are assignable. + + If it checks out, JSHINT returns true. Otherwise, it returns false. + + If false, you can inspect JSHINT.errors to find out the problems. + JSHINT.errors is an array of objects containing these members: + + { + line : The line (relative to 1) at which the lint was found + character : The character (relative to 1) at which the lint was found + reason : The problem + evidence : The text line in which the problem occurred + raw : The raw message before the details were inserted + a : The first detail + b : The second detail + c : The third detail + d : The fourth detail + } + + If a fatal error was found, a null will be the last element of the + JSHINT.errors array. + + You can request a data structure which contains JSHint's results. + + var myData = JSHINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + character: NUMBER, + last: NUMBER, + lastcharacter: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unused: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + +*/ + +/*jshint + evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true, + undef: true, maxlen: 100, indent: 4, quotmark: double, unused: true +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)", + "(breakage)", "(character)", "(context)", "(error)", "(explicitNewcap)", "(global)", + "(identifier)", "(last)", "(lastcharacter)", "(line)", "(loopage)", "(metrics)", + "(name)", "(onevar)", "(params)", "(scope)", "(statement)", "(verb)", "(tokens)", "(catch)", + "*", "+", "++", "-", "--", "\/", "<", "<=", "==", + "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax, + __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio, + Autocompleter, Asset, Boolean, Builder, Buffer, Browser, Blob, COM, CScript, Canvas, + CustomAnimation, Class, Control, ComplexityCount, Chain, Color, Cookie, Core, DataView, Date, + Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMEvent, DOMReady, DOMParser, + Drag, E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event, + Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form, + FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey, + HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement, + HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement, + HTMLDivElement, HTMLDListElement, HTMLFieldSetElement, + HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement, + HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement, + HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement, + HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement, + HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement, + HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement, + HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement, + HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement, + HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement, + HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement, + HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement, + Iframe, IframeShim, Image, importScripts, Int16Array, Int32Array, Int8Array, + Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E, + MAX_VALUE, MIN_VALUE, Map, Mask, Math, MenuItem, MessageChannel, MessageEvent, MessagePort, + MoveAnimation, MooTools, MutationObserver, NaN, Native, NEGATIVE_INFINITY, Node, NodeFilter, + Number, Object, ObjectRange, + Option, Options, OverText, PI, POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, + RangeError, Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, Set, + SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion, + ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller, + Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables, + SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template, + Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL, + VBArray, WeakMap, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XMLSerializer, + XPathEvaluator, XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, + "\\", a, abs, addEventListener, address, alert, apply, applicationCache, arguments, arity, + asi, atob, b, basic, basicToken, bitwise, blacklist, block, blur, boolOptions, boss, + browser, btoa, c, call, callee, caller, camelcase, cases, charAt, charCodeAt, character, + clearInterval, clearTimeout, close, closed, closure, comment, complexityCount, condition, + confirm, console, constructor, content, couch, create, css, curly, d, data, datalist, dd, debug, + decodeURI, decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document, + dojo, dijit, dojox, define, else, emit, encodeURI, encodeURIComponent, elem, + eqeq, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil, + ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus, forEach, + forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions, + g, gc, getComputedStyle, getRow, getter, getterToken, GLOBAL, global, globals, globalstrict, + hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include, + indent, indexOf, init, ins, internals, instanceOf, isAlpha, isApplicationRunning, isArray, + isDigit, isFinite, isNaN, iterator, java, join, jshint, + JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastcharacter, lastsemic, laxbreak, + laxcomma, latedef, lbp, led, left, length, line, load, loadClass, localStorage, location, + log, loopfunc, m, match, max, maxcomplexity, maxdepth, maxerr, maxlen, maxstatements, maxparams, + member, message, meta, module, moveBy, moveTo, mootools, multistr, name, navigator, new, newcap, + nestedBlockDepth, noarg, node, noempty, nomen, nonew, nonstandard, nud, onbeforeunload, onblur, + onerror, onevar, onecase, onfocus, onload, onresize, onunload, open, openDatabase, openURL, + opener, opera, options, outer, param, parent, parseFloat, parseInt, passfail, plusplus, + postMessage, pop, predef, print, process, prompt, proto, prototype, prototypejs, provides, push, + quit, quotmark, range, raw, reach, reason, regexp, readFile, readUrl, regexdash, + removeEventListener, replace, report, require, reserved, resizeBy, resizeTo, resolvePath, + resumeUpdates, respond, rhino, right, runCommand, scroll, scope, screen, scripturl, scrollBy, + scrollTo, scrollbar, search, seal, self, send, serialize, sessionStorage, setInterval, setTimeout, + setter, setterToken, shift, slice, smarttabs, sort, spawn, split, statement, statementCount, stack, + status, start, strict, sub, substr, supernew, shadow, supplant, sum, sync, test, toLowerCase, + toString, toUpperCase, toint32, token, tokens, top, trailing, type, typeOf, Uint16Array, + Uint32Array, Uint8Array, undef, undefs, unused, urls, validthis, value, valueOf, var, vars, + version, verifyMaxParametersPerFunction, verifyMaxStatementsPerFunction, + verifyMaxComplexityPerFunction, verifyMaxNestedBlockDepthPerFunction, WebSocket, withstmt, white, + window, windows, Worker, worker, wsh, yui, YUI, Y, YUI_config*/ + +/*global exports: false */ + +// We build the application inside a function so that we produce only a single +// global variable. That function will be invoked immediately, and its return +// value is the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + +// These are operators that should not be used with the ! operator. + + bang = { + "<" : true, + "<=" : true, + "==" : true, + "===": true, + "!==": true, + "!=" : true, + ">" : true, + ">=" : true, + "+" : true, + "-" : true, + "*" : true, + "/" : true, + "%" : true + }, + + // These are the JSHint boolean options. + boolOptions = { + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments should be allowed + browser : true, // if the standard browser globals should be predefined + camelcase : true, // if identifiers should be required in camel case + couch : true, // if CouchDB globals should be predefined + curly : true, // if curly braces around all blocks should be required + debug : true, // if debugger statements should be allowed + devel : true, // if logging globals should be predefined (console, + // alert, etc.) + dojo : true, // if Dojo Toolkit globals should be predefined + eqeqeq : true, // if === should be required + eqnull : true, // if == null comparisons should be tolerated + es5 : true, // if ES5 syntax should be allowed + esnext : true, // if es.next specific syntax should be allowed + evil : true, // if eval should be allowed + expr : true, // if ExpressionStatement should be allowed as Programs + forin : true, // if for in statements must filter + funcscope : true, // if only function scope should be used for scope tests + globalstrict: true, // if global "use strict"; should be allowed (also + // enables 'strict') + immed : true, // if immediate invocations must be wrapped in parens + iterator : true, // if the `__iterator__` property should be allowed + jquery : true, // if jQuery globals should be predefined + lastsemic : true, // if semicolons may be ommitted for the trailing + // statements inside of a one-line blocks. + latedef : true, // if the use before definition should not be tolerated + laxbreak : true, // if line breaks should not be checked + laxcomma : true, // if line breaks should not be checked around commas + loopfunc : true, // if functions should be allowed to be defined within + // loops + mootools : true, // if MooTools globals should be predefined + multistr : true, // allow multiline strings + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be + // disallowed + node : true, // if the Node.js environment globals should be + // predefined + noempty : true, // if empty blocks should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nonstandard : true, // if non-standard (but widely adopted) globals should + // be predefined + nomen : true, // if names should be checked + onevar : true, // if only one var statement per function should be + // allowed + onecase : true, // if one case switch statements should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + proto : true, // if the `__proto__` property should be allowed + prototypejs : true, // if Prototype and Scriptaculous globals should be + // predefined + regexdash : true, // if unescaped first/last dash (-) inside brackets + // should be tolerated + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + unused : true, // if variables should be always used + scripturl : true, // if script-targeted URLs should be tolerated + shadow : true, // if variable shadowing should be tolerated + smarttabs : true, // if smarttabs should be tolerated + // (http://www.emacswiki.org/emacs/SmartTabs) + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + supernew : true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing : true, // if trailing whitespace rules apply + validthis : true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + withstmt : true, // if with statements should be allowed + white : true, // if strict whitespace rules apply + worker : true, // if Web Worker script symbols should be allowed + wsh : true, // if the Windows Scripting Host environment globals + // should be predefined + yui : true // YUI variables should be predefined + }, + + // These are the JSHint options that can take any value + // (we use this object to detect invalid options) + valOptions = { + maxlen : false, + indent : false, + maxerr : false, + predef : false, + quotmark : false, //'single'|'double'|true + scope : false, + maxstatements: false, // {int} max statements per function + maxdepth : false, // {int} max nested block depth per function + maxparams : false, // {int} max params per function + maxcomplexity: false // {int} max cyclomatic complexity per function + }, + + // These are JSHint boolean options which are shared with JSLint + // where the definition in JSHint is opposite JSLint + invertedOptions = { + bitwise : true, + forin : true, + newcap : true, + nomen : true, + plusplus : true, + regexp : true, + undef : true, + white : true, + + // Inverted and renamed, use JSHint name here + eqeqeq : true, + onevar : true + }, + + // These are JSHint boolean options which are shared with JSLint + // where the name has been changed but the effect is unchanged + renamedOptions = { + eqeq : "eqeqeq", + vars : "onevar", + windows : "wsh" + }, + + + // browser contains a set of global names which are commonly provided by a + // web browser environment. + browser = { + ArrayBuffer : false, + ArrayBufferView : false, + Audio : false, + Blob : false, + addEventListener : false, + applicationCache : false, + atob : false, + blur : false, + btoa : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + DataView : false, + DOMParser : false, + defaultStatus : false, + document : false, + event : false, + FileReader : false, + Float32Array : false, + Float64Array : false, + FormData : false, + focus : false, + frames : false, + getComputedStyle : false, + HTMLElement : false, + HTMLAnchorElement : false, + HTMLBaseElement : false, + HTMLBlockquoteElement : false, + HTMLBodyElement : false, + HTMLBRElement : false, + HTMLButtonElement : false, + HTMLCanvasElement : false, + HTMLDirectoryElement : false, + HTMLDivElement : false, + HTMLDListElement : false, + HTMLFieldSetElement : false, + HTMLFontElement : false, + HTMLFormElement : false, + HTMLFrameElement : false, + HTMLFrameSetElement : false, + HTMLHeadElement : false, + HTMLHeadingElement : false, + HTMLHRElement : false, + HTMLHtmlElement : false, + HTMLIFrameElement : false, + HTMLImageElement : false, + HTMLInputElement : false, + HTMLIsIndexElement : false, + HTMLLabelElement : false, + HTMLLayerElement : false, + HTMLLegendElement : false, + HTMLLIElement : false, + HTMLLinkElement : false, + HTMLMapElement : false, + HTMLMenuElement : false, + HTMLMetaElement : false, + HTMLModElement : false, + HTMLObjectElement : false, + HTMLOListElement : false, + HTMLOptGroupElement : false, + HTMLOptionElement : false, + HTMLParagraphElement : false, + HTMLParamElement : false, + HTMLPreElement : false, + HTMLQuoteElement : false, + HTMLScriptElement : false, + HTMLSelectElement : false, + HTMLStyleElement : false, + HTMLTableCaptionElement : false, + HTMLTableCellElement : false, + HTMLTableColElement : false, + HTMLTableElement : false, + HTMLTableRowElement : false, + HTMLTableSectionElement : false, + HTMLTextAreaElement : false, + HTMLTitleElement : false, + HTMLUListElement : false, + HTMLVideoElement : false, + history : false, + Int16Array : false, + Int32Array : false, + Int8Array : false, + Image : false, + length : false, + localStorage : false, + location : false, + MessageChannel : false, + MessageEvent : false, + MessagePort : false, + moveBy : false, + moveTo : false, + MutationObserver : false, + name : false, + Node : false, + NodeFilter : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener : false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + sessionStorage : false, + setInterval : false, + setTimeout : false, + SharedWorker : false, + status : false, + top : false, + Uint16Array : false, + Uint32Array : false, + Uint8Array : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false, + XMLSerializer : false, + XPathEvaluator : false, + XPathException : false, + XPathExpression : false, + XPathNamespace : false, + XPathNSResolver : false, + XPathResult : false + }, + + couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false, + provides : false + }, + + declared, // Globals that were declared using /*global ... */ syntax. + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + dojo = { + dojo : false, + dijit : false, + dojox : false, + define : false, + "require" : false + }, + + funct, // The current function + + functionicity = [ + "closure", "exception", "global", "label", + "outer", "unused", "var" + ], + + functions, // All of the functions + + global, // The global scope + implied, // Implied globals + inblock, + indent, + jsonmode, + + jquery = { + "$" : false, + jQuery : false + }, + + lines, + lookahead, + member, + membersOnly, + + mootools = { + "$" : false, + "$$" : false, + Asset : false, + Browser : false, + Chain : false, + Class : false, + Color : false, + Cookie : false, + Core : false, + Document : false, + DomReady : false, + DOMEvent : false, + DOMReady : false, + Drag : false, + Element : false, + Elements : false, + Event : false, + Events : false, + Fx : false, + Group : false, + Hash : false, + HtmlTable : false, + Iframe : false, + IframeShim : false, + InputValidator : false, + instanceOf : false, + Keyboard : false, + Locale : false, + Mask : false, + MooTools : false, + Native : false, + Options : false, + OverText : false, + Request : false, + Scroller : false, + Slick : false, + Slider : false, + Sortables : false, + Spinner : false, + Swiff : false, + Tips : false, + Type : false, + typeOf : false, + URI : false, + Window : false + }, + + nexttoken, + + node = { + __filename : false, + __dirname : false, + Buffer : false, + console : false, + exports : true, // In Node it is ok to exports = module.exports = foo(); + GLOBAL : false, + global : false, + module : false, + process : false, + require : false, + setTimeout : false, + clearTimeout : false, + setInterval : false, + clearInterval : false + }, + + noreach, + option, + predefined, // Global variables defined by option + prereg, + prevtoken, + + prototypejs = { + "$" : false, + "$$" : false, + "$A" : false, + "$F" : false, + "$H" : false, + "$R" : false, + "$break" : false, + "$continue" : false, + "$w" : false, + Abstract : false, + Ajax : false, + Class : false, + Enumerable : false, + Element : false, + Event : false, + Field : false, + Form : false, + Hash : false, + Insertion : false, + ObjectRange : false, + PeriodicalExecuter: false, + Position : false, + Prototype : false, + Selector : false, + Template : false, + Toggle : false, + Try : false, + Autocompleter : false, + Builder : false, + Control : false, + Draggable : false, + Draggables : false, + Droppables : false, + Effect : false, + Sortable : false, + SortableObserver : false, + Sound : false, + Scriptaculous : false + }, + + quotmark, + + rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + importPackage: false, + "java" : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false + }, + + scope, // The current scope + stack, + + // standard contains the global names that are provided by the + // ECMAScript standard. + standard = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + "eval" : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Map : false, + Math : false, + NaN : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + Set : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false, + WeakMap : false + }, + + // widely adopted global names that are not part of ECMAScript standard + nonstandard = { + escape : false, + unescape : false + }, + + directive, + syntax = {}, + tab, + token, + unuseds, + urls, + useESNextSyntax, + warnings, + + worker = { + importScripts : true, + postMessage : true, + self : true + }, + + wsh = { + ActiveXObject : true, + Enumerator : true, + GetObject : true, + ScriptEngine : true, + ScriptEngineBuildVersion : true, + ScriptEngineMajorVersion : true, + ScriptEngineMinorVersion : true, + VBArray : true, + WSH : true, + WScript : true, + XDomainRequest : true + }, + + yui = { + YUI : false, + Y : false, + YUI_config : false + }; + // Regular expressions. Some of these are stupidly long. + var ax, cx, tx, nx, nxg, lx, ix, jx, ft; + (function () { + /*jshint maxlen:300 */ + + // unsafe comment or string + ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + + // unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + + // token + tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/=(?!(\S*\/[gim]?))|\/(\*(jshint|jslint|members?|global)?|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/; + + // characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + // star slash + lx = /\*\//; + + // identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + + // javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + // catches /* falls through */ comments + ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/; + }()); + + function F() {} // Used by Object.create + + function is_own(object, name) { + // The object.hasOwnProperty method fails when the property under consideration + // is named 'hasOwnProperty'. So we have to use this more convoluted form. + return Object.prototype.hasOwnProperty.call(object, name); + } + + function checkOption(name, t) { + if (valOptions[name] === undefined && boolOptions[name] === undefined) { + warning("Bad option: '" + name + "'.", t); + } + } + + function isString(obj) { + return Object.prototype.toString.call(obj) === "[object String]"; + } + + // Provide critical ES5 functions to ES3. + + if (typeof Array.isArray !== "function") { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === "[object Array]"; + }; + } + + if (!Array.prototype.forEach) { + Array.prototype.forEach = function (fn, scope) { + var len = this.length; + + for (var i = 0; i < len; i++) { + fn.call(scope || this, this[i], i, this); + } + }; + } + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + if (this === null || this === undefined) { + throw new TypeError(); + } + + var t = new Object(this); + var len = t.length >>> 0; + + if (len === 0) { + return -1; + } + + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n !== 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + + if (n >= len) { + return -1; + } + + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + + return -1; + }; + } + + if (typeof Object.create !== "function") { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== "function") { + Object.keys = function (o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + }; + } + + // Non standard methods + + function isAlpha(str) { + return (str >= "a" && str <= "z\uffff") || + (str >= "A" && str <= "Z\uffff"); + } + + function isDigit(str) { + return (str >= "0" && str <= "9"); + } + + function isIdentifier(token, value) { + if (!token) + return false; + + if (!token.identifier || token.value !== value) + return false; + + return true; + } + + function supplant(str, data) { + return str.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = data[b]; + return typeof r === "string" || typeof r === "number" ? r : a; + }); + } + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n) && !is_own(JSHINT.blacklist, n)) { + t[n] = o[n]; + } + } + } + + function updatePredefined() { + Object.keys(JSHINT.blacklist).forEach(function (key) { + delete predefined[key]; + }); + } + + function assume() { + if (option.couch) { + combine(predefined, couch); + } + + if (option.rhino) { + combine(predefined, rhino); + } + + if (option.prototypejs) { + combine(predefined, prototypejs); + } + + if (option.node) { + combine(predefined, node); + option.globalstrict = true; + } + + if (option.devel) { + combine(predefined, devel); + } + + if (option.dojo) { + combine(predefined, dojo); + } + + if (option.browser) { + combine(predefined, browser); + } + + if (option.nonstandard) { + combine(predefined, nonstandard); + } + + if (option.jquery) { + combine(predefined, jquery); + } + + if (option.mootools) { + combine(predefined, mootools); + } + + if (option.worker) { + combine(predefined, worker); + } + + if (option.wsh) { + combine(predefined, wsh); + } + + if (option.esnext) { + useESNextSyntax(); + } + + if (option.globalstrict && option.strict !== false) { + option.strict = true; + } + + if (option.yui) { + combine(predefined, yui); + } + } + + + // Produce an error warning. + function quit(message, line, chr) { + var percentage = Math.floor((line / lines.length) * 100); + + throw { + name: "JSHintError", + line: line, + character: chr, + message: message + " (" + percentage + "% scanned).", + raw: message + }; + } + + function isundef(scope, m, t, a) { + return JSHINT.undefs.push([scope, m, t, a]); + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === "(end)") { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: "(error)", + raw: m, + evidence: lines[l - 1] || "", + line: l, + character: ch, + scope: JSHINT.scope, + a: a, + b: b, + c: c, + d: d + }; + w.reason = supplant(m, w); + JSHINT.errors.push(w); + if (option.passfail) { + quit("Stopping. ", l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + // Tracking of "internal" scripts, like eval containing a static string + function addInternalSrc(elem, src) { + var i; + i = { + id: "(internal)", + elem: elem, + value: src + }; + JSHINT.internals.push(i); + return i; + } + + +// lexical analysis and token construction + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at, + match, + tw; // trailing whitespace check + + if (line >= lines.length) + return false; + + character = 1; + s = lines[line]; + line += 1; + + // If smarttabs option is used check for spaces followed by tabs only. + // Otherwise check for any occurence of mixed tabs and spaces. + // Tabs and one space followed by block comment is allowed. + if (option.smarttabs) { + // negative look-behind for "//" + match = s.match(/(\/\/)? \t/); + at = match && !match[1] ? 0 : -1; + } else { + at = s.search(/ \t|\t [^\*]/); + } + + if (at >= 0) + warningAt("Mixed spaces and tabs.", line, at + 1); + + s = s.replace(/\t/g, tab); + at = s.search(cx); + + if (at >= 0) + warningAt("Unsafe character.", line, at); + + if (option.maxlen && option.maxlen < s.length) + warningAt("Line too long.", line, s.length); + + // Check for trailing whitespaces + tw = option.trailing && s.match(/^(.*?)\s+$/); + if (tw && !/^\s+$/.test(s)) { + warningAt("Trailing whitespace.", line, tw[1].length + 1); + } + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + + function checkName(name) { + if (!option.proto && name === "__proto__") { + warningAt("The '{a}' property is deprecated.", line, from, name); + return; + } + + if (!option.iterator && name === "__iterator__") { + warningAt("'{a}' is only available in JavaScript 1.7.", line, from, name); + return; + } + + // Check for dangling underscores unless we're in Node + // environment and this identifier represents built-in + // Node globals with underscores. + + var hasDangling = /^(_+.*|.*_+)$/.test(name); + + if (option.nomen && hasDangling && name !== "_") { + if (option.node && token.id !== "." && /^(__dirname|__filename)$/.test(name)) + return; + + warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", name); + return; + } + + // Check for non-camelcase names. Names like MY_VAR and + // _myVar are okay though. + + if (option.camelcase) { + if (name.replace(/^_+/, "").indexOf("_") > -1 && !name.match(/^[A-Z0-9_]*$/)) { + warningAt("Identifier '{a}' is not in camel case.", line, from, value); + } + } + } + + if (type === "(color)" || type === "(range)") { + t = {type: type}; + } else if (type === "(punctuator)" || + (type === "(identifier)" && is_own(syntax, value))) { + t = syntax[value] || syntax["(error)"]; + } else { + t = syntax[type]; + } + + t = Object.create(t); + + if (type === "(string)" || type === "(range)") { + if (!option.scripturl && jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + + if (type === "(identifier)") { + t.identifier = true; + checkName(value); + } + + t.value = value; + t.line = line; + t.character = character; + t.from = from; + i = t.id; + if (i !== "(endline)") { + prereg = i && + (("(,=:[!&|?{};".indexOf(i.charAt(i.length - 1)) >= 0) || + i === "return" || + i === "case"); + } + return t; + } + + // Public lex methods + return { + init: function (source) { + if (typeof source === "string") { + lines = source + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + .split("\n"); + } else { + lines = source; + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + if (lines[0] && lines[0].substr(0, 2) === "#!") + lines[0] = ""; + + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ""; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case "": + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it("(range)", value); + case "\\": + warningAt("Unexpected '{a}'.", line, character, c); + } + value += c; + } + + }, + + + // token -- this is called by advance to get the next token + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange, n; + + function match(x) { + var r = x.exec(s), r1; + + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = "", allowNewLine = false; + + if (jsonmode && x !== "\"") { + warningAt("Strings must use doublequote.", + line, character); + } + + if (option.quotmark) { + if (option.quotmark === "single" && x !== "'") { + warningAt("Strings must use singlequote.", + line, character); + } else if (option.quotmark === "double" && x !== "\"") { + warningAt("Strings must use doublequote.", + line, character); + } else if (option.quotmark === true) { + quotmark = quotmark || x; + if (quotmark !== x) { + warningAt("Mixed double and single quotes.", + line, character); + } + } + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + + j = 0; +unclosedString: for (;;) { + while (j >= s.length) { + j = 0; + + var cl = line, cf = from; + if (!nextLine()) { + errorAt("Unclosed string.", cl, cf); + break unclosedString; + } + + if (allowNewLine) { + allowNewLine = false; + } else { + warningAt("Unclosed string.", cl, cf); + } + } + + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it("(string)", r, x); + } + + if (c < " ") { + if (c === "\n" || c === "\r") { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === "\\") { + j += 1; + character += 1; + c = s.charAt(j); + n = s.charAt(j + 1); + switch (c) { + case "\\": + case "\"": + case "/": + break; + case "\'": + if (jsonmode) { + warningAt("Avoid \\'.", line, character); + } + break; + case "b": + c = "\b"; + break; + case "f": + c = "\f"; + break; + case "n": + c = "\n"; + break; + case "r": + c = "\r"; + break; + case "t": + c = "\t"; + break; + case "0": + c = "\0"; + // Octal literals fail in strict mode + // check if the number is between 00 and 07 + // where 'n' is the token next to 'c' + if (n >= 0 && n <= 7 && directive["use strict"]) { + warningAt( + "Octal literals are not allowed in strict mode.", + line, character); + } + break; + case "u": + esc(4); + break; + case "v": + if (jsonmode) { + warningAt("Avoid \\v.", line, character); + } + c = "\v"; + break; + case "x": + if (jsonmode) { + warningAt("Avoid \\x-.", line, character); + } + esc(2); + break; + case "": + // last character is escape character + // always allow new line if escaped, but show + // warning if option is not set + allowNewLine = true; + if (option.multistr) { + if (jsonmode) { + warningAt("Avoid EOL escapement.", line, character); + } + c = ""; + character -= 1; + break; + } + warningAt("Bad escapement of EOL. Use option multistr if needed.", + line, character); + break; + case "!": + if (s.charAt(j - 2) === "<") + break; + /*falls through*/ + default: + warningAt("Bad escapement.", line, character); + } + } + r += c; + character += 1; + j += 1; + } + } + + for (;;) { + if (!s) { + return it(nextLine() ? "(endline)" : "(end)", ""); + } + + t = match(tx); + + if (!t) { + t = ""; + c = ""; + while (s && s < "!") { + s = s.substr(1); + } + if (s) { + errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1)); + s = ""; + } + } else { + + // identifier + + if (isAlpha(c) || c === "_" || c === "$") { + return it("(identifier)", t); + } + + // number + + if (isDigit(c)) { + if (!isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (isAlpha(s.substr(0, 1))) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === "0") { + d = t.substr(1, 1); + if (isDigit(d)) { + if (token.id !== ".") { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === "x" || d === "X")) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === ".") { + warningAt( +"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); + } + return it("(number)", t); + } + switch (t) { + + // string + + case "\"": + case "'": + return string(t); + + // // comment + + case "//": + s = ""; + token.comment = true; + break; + + // /* comment + + case "/*": + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jshint /*global + + case "/*members": + case "/*member": + case "/*jshint": + case "/*jslint": + case "/*global": + case "*/": + return { + value: t, + type: "special", + line: line, + character: character, + from: from + }; + + case "": + break; + // / + case "/": + if (s.charAt(0) === "=") { + errorAt("A regular expression literal can be confused with '/='.", + line, from); + } + + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case "": + errorAt("Unclosed regular expression.", line, from); + return quit("Stopping.", line, from); + case "/": + if (depth > 0) { + warningAt("{a} unterminated regular expression " + + "group(s).", line, from + l, depth); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === "/" || q === "*") { + errorAt("Confusing regular expression.", + line, from); + } + return it("(regexp)", c); + case "\\": + c = s.charAt(l); + if (c < " ") { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === "<") { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case "(": + depth += 1; + b = false; + if (s.charAt(l) === "?") { + l += 1; + switch (s.charAt(l)) { + case ":": + case "=": + case "!": + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ":", s.charAt(l)); + } + } else { + captures += 1; + } + break; + case "|": + b = false; + break; + case ")": + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ")"); + } else { + depth -= 1; + } + break; + case " ": + q = 1; + while (s.charAt(l) === " ") { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case "[": + c = s.charAt(l); + if (c === "^") { + l += 1; + if (s.charAt(l) === "]") { + errorAt("Unescaped '{a}'.", + line, from + l, "^"); + } + } + if (c === "]") { + warningAt("Empty class.", line, + from + l - 1); + } + isLiteral = false; + isInRange = false; +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case "[": + case "^": + warningAt("Unescaped '{a}'.", + line, from + l, c); + if (isInRange) { + isInRange = false; + } else { + isLiteral = true; + } + break; + case "-": + if (isLiteral && !isInRange) { + isLiteral = false; + isInRange = true; + } else if (isInRange) { + isInRange = false; + } else if (s.charAt(l) === "]") { + isInRange = true; + } else { + if (option.regexdash !== (l === 2 || (l === 3 && + s.charAt(1) === "^"))) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, "-"); + } + isLiteral = true; + } + break; + case "]": + if (isInRange && !option.regexdash) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, "-"); + } + break klass; + case "\\": + c = s.charAt(l); + if (c < " ") { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === "<") { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + + // \w, \s and \d are never part of a character range + if (/[wsd]/i.test(c)) { + if (isInRange) { + warningAt("Unescaped '{a}'.", + line, from + l, "-"); + isInRange = false; + } + isLiteral = false; + } else if (isInRange) { + isInRange = false; + } else { + isLiteral = true; + } + break; + case "/": + warningAt("Unescaped '{a}'.", + line, from + l - 1, "/"); + + if (isInRange) { + isInRange = false; + } else { + isLiteral = true; + } + break; + case "<": + if (isInRange) { + isInRange = false; + } else { + isLiteral = true; + } + break; + default: + if (isInRange) { + isInRange = false; + } else { + isLiteral = true; + } + } + } while (c); + break; + case ".": + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case "]": + case "?": + case "{": + case "}": + case "+": + case "*": + warningAt("Unescaped '{a}'.", line, + from + l, c); + } + if (b) { + switch (s.charAt(l)) { + case "?": + case "+": + case "*": + l += 1; + if (s.charAt(l) === "?") { + l += 1; + } + break; + case "{": + l += 1; + c = s.charAt(l); + if (c < "0" || c > "9") { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + break; // No reason to continue checking numbers. + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < "0" || c > "9") { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ",") { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= "0" && c <= "9") { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < "0" || c > "9") { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== "}") { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, "}", c); + } else { + l += 1; + } + if (s.charAt(l) === "?") { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it("(regexp)", c); + } + return it("(punctuator)", t); + + // punctuator + + case "#": + return it("(punctuator)", t); + default: + return it("(punctuator)", t); + } + } + } + } + }; + }()); + + + function addlabel(t, type, token) { + if (t === "hasOwnProperty") { + warning("'hasOwnProperty' is a really bad name."); + } + + // Define t in the current function in the current scope. + if (type === "exception") { + if (is_own(funct["(context)"], t)) { + if (funct[t] !== true && !option.node) { + warning("Value of '{a}' may be overwritten in IE.", nexttoken, t); + } + } + } + + if (is_own(funct, t) && !funct["(global)"]) { + if (funct[t] === true) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + } else { + if (!option.shadow && type !== "exception") { + warning("'{a}' is already defined.", nexttoken, t); + } + } + } + + funct[t] = type; + + if (token) { + funct["(tokens)"][t] = token; + } + + if (funct["(global)"]) { + global[t] = funct; + if (is_own(implied, t)) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var nt = nexttoken; + var o = nt.value; + var quotmarkValue = option.quotmark; + var predef = {}; + var b, obj, filter, t, tn, v, minus; + + switch (o) { + case "*/": + error("Unbegun comment."); + break; + case "/*members": + case "/*member": + o = "/*members"; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + option.quotmark = false; + break; + case "/*jshint": + case "/*jslint": + obj = option; + filter = boolOptions; + break; + case "/*global": + obj = predef; + break; + default: + error("What?"); + } + + t = lex.token(); +loop: for (;;) { + minus = false; + for (;;) { + if (t.type === "special" && t.value === "*/") { + break loop; + } + if (t.id !== "(endline)" && t.id !== ",") { + break; + } + t = lex.token(); + } + + if (o === "/*global" && t.value === "-") { + minus = true; + t = lex.token(); + } + + if (t.type !== "(string)" && t.type !== "(identifier)" && o !== "/*members") { + error("Bad option.", t); + } + + v = lex.token(); + if (v.id === ":") { + v = lex.token(); + + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", t, "*/", ":"); + } + + if (o === "/*jshint") { + checkOption(t.value, t); + } + + var numericVals = [ + "maxstatements", + "maxparams", + "maxdepth", + "maxcomplexity", + "maxerr", + "maxlen", + "indent" + ]; + + if (numericVals.indexOf(t.value) > -1 && (o === "/*jshint" || o === "/*jslint")) { + b = +v.value; + + if (typeof b !== "number" || !isFinite(b) || b <= 0 || Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", v, v.value); + } + + if (t.value === "indent") + obj.white = true; + + obj[t.value] = b; + } else if (t.value === "validthis") { + if (funct["(global)"]) { + error("Option 'validthis' can't be used in a global scope."); + } else { + if (v.value === "true" || v.value === "false") + obj[t.value] = v.value === "true"; + else + error("Bad option value.", v); + } + } else if (t.value === "quotmark" && (o === "/*jshint")) { + switch (v.value) { + case "true": + obj.quotmark = true; + break; + case "false": + obj.quotmark = false; + break; + case "double": + case "single": + obj.quotmark = v.value; + break; + default: + error("Bad option value.", v); + } + } else if (v.value === "true" || v.value === "false") { + if (o === "/*jslint") { + tn = renamedOptions[t.value] || t.value; + obj[tn] = v.value === "true"; + if (invertedOptions[tn] !== undefined) { + obj[tn] = !obj[tn]; + } + } else { + obj[t.value] = v.value === "true"; + } + + if (t.value === "newcap") + obj["(explicitNewcap)"] = true; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === "/*jshint" || o === "/*jslint") { + error("Missing option value.", t); + } + + obj[t.value] = false; + + if (o === "/*global" && minus === true) { + JSHINT.blacklist[t.value] = t.value; + updatePredefined(); + } + + t = v; + } + } + + if (o === "/*members") { + option.quotmark = quotmarkValue; + } + + combine(predefined, predef); + + for (var key in predef) { + if (is_own(predef, key)) { + declared[key] = nt; + } + } + + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case "(number)": + if (nexttoken.id === ".") { + warning("A dot following a number can be confused with a decimal point.", token); + } + break; + case "-": + if (nexttoken.id === "-" || nexttoken.id === "--") { + warning("Confusing minusses."); + } + break; + case "+": + if (nexttoken.id === "+" || nexttoken.id === "++") { + warning("Confusing plusses."); + } + break; + } + + if (token.type === "(string)" || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === "(end)") { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== "(identifier)" || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.id === "(end)" || nexttoken.id === "(error)") { + return; + } + if (nexttoken.type === "special") { + doOption(); + } else { + if (nexttoken.id !== "(endline)") { + break; + } + } + } + } + + +// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is +// like .nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define statement-oriented languages like +// JavaScript. I retained Pratt's nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, isArray = false, isObject = false; + + if (nexttoken.id === "(end)") + error("Unexpected early end of program.", token); + + advance(); + if (initial) { + anonname = "anonymous"; + funct["(verb)"] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === "(number)" && token.id === ".") { + warning("A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + isArray = token.value === "Array"; + isObject = token.value === "Object"; + + // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() + // Line breaks in IfStatement heads exist to satisfy the checkJSHint + // "Line too long." error. + if (left && (left.value || (left.first && left.first.value))) { + // If the left.value is not "new", or the left.first.value is a "." + // then safely assume that this is not "new Array()" and possibly + // not "new Object()"... + if (left.value !== "new" || + (left.first && left.first.value && left.first.value === ".")) { + isArray = false; + // ...In the case of Object, if the left.value and token.value + // are not equal, then safely assume that this not "new Object()" + if (left.value !== token.value) { + isObject = false; + } + } + } + + advance(); + if (isArray && token.id === "(" && nexttoken.id === ")") + warning("Use the array literal notation [].", token); + if (isObject && token.id === "(" && nexttoken.id === ")") + warning("Use the object literal notation {}.", token); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white) { + if (left.character !== right.from && left.line === right.line) { + left.from += (left.character - left.from); + warning("Unexpected space after '{a}'.", left, left.value); + } + } + } + + function nobreak(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && (left.character !== right.from || left.line !== right.line)) { + warning("Unexpected space before '{a}'.", right, right.value); + } + } + + function nospace(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.value === ";" && right.value === ";") { + return; + } + if (left.line === right.line && left.character === right.from) { + left.from += (left.character - left.from); + warning("Missing space after '{a}'.", + left, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (!option.laxbreak && left.line !== right.line) { + warning("Bad line breaking before '{a}'.", right, right.id); + } else if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.character === right.from) { + left.from += (left.character - left.from); + warning("Missing space after '{a}'.", + left, left.value); + } + } + } + + function indentation(bias) { + var i; + if (option.white && nexttoken.id !== "(end)") { + i = indent + (bias || 0); + if (nexttoken.from !== i) { + warning( +"Expected '{a}' to have an indentation at {b} instead at {c}.", + nexttoken, nexttoken.value, i, nexttoken.from); + } + } + } + + function nolinebreak(t) { + t = t || token; + if (t.line !== nexttoken.line) { + warning("Line breaking error '{a}'.", t, t.value); + } + } + + + function comma() { + if (token.line !== nexttoken.line) { + if (!option.laxcomma) { + if (comma.first) { + warning("Comma warnings can be turned off with 'laxcomma'"); + comma.first = false; + } + warning("Bad line breaking before '{a}'.", token, nexttoken.id); + } + } else if (!token.comment && token.character !== nexttoken.from && option.white) { + token.from += (token.character - token.from); + warning("Unexpected space after '{a}'.", token, token.value); + } + advance(","); + nonadjacent(token, nexttoken); + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== "object") { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === "function") ? f : function () { + this.right = expression(150); + this.arity = "unary"; + if (this.id === "++" || this.id === "--") { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.right.identifier || this.right.reserved) && + this.right.id !== "." && this.right.id !== "[") { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === "function") { + v(this); + } + return this; + }); + } + + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + } + if (s === "in" && left.id === "!") { + warning("Confusing use of '{a}'.", left, "!"); + } + if (typeof f === "function") { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function relation(s, f) { + var x = symbol(s, 100); + x.led = function (left) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + var right = expression(100); + + if (isIdentifier(left, "NaN") || isIdentifier(right, "NaN")) { + warning("Use the isNaN function to compare with NaN.", this); + } else if (f) { + f.apply(this, [left, right]); + } + if (left.id === "!") { + warning("Confusing use of '{a}'.", left, "!"); + } + if (right.id === "!") { + warning("Confusing use of '{a}'.", right, "!"); + } + this.left = left; + this.right = right; + return this; + }; + return x; + } + + + function isPoorRelation(node) { + return node && + ((node.type === "(number)" && +node.value === 0) || + (node.type === "(string)" && node.value === "") || + (node.type === "null" && !option.eqnull) || + node.type === "true" || + node.type === "false" || + node.type === "undefined"); + } + + + function assignop(s) { + symbol(s, 20).exps = true; + + return infix(s, function (left, that) { + that.left = left; + + if (predefined[left.value] === false && + scope[left.value]["(global)"] === true) { + warning("Read only.", left); + } else if (left["function"]) { + warning("'{a}' is a function.", left, left.value); + } + + if (left) { + if (option.esnext && funct[left.value] === "const") { + warning("Attempting to override '{a}' which is a constant", left, left.value); + } + + if (left.id === "." || left.id === "[") { + if (!left.left || left.left.value === "arguments") { + warning("Bad assignment.", that); + } + that.right = expression(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === "exception") { + warning("Do not assign to the exception parameter.", left); + } + that.right = expression(19); + return that; + } + + if (left === syntax["function"]) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + + error("Bad assignment.", that); + }, 20); + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === "function") ? f : function (left) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + nonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + if (left) { + if (left.id === "." || left.id === "[" || + (left.identifier && !left.reserved)) { + expression(19); + return that; + } + if (left === syntax["function"]) { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + token); + } + return that; + } + error("Bad assignment.", that); + }, 20); + } + + + function suffix(s) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== "." && left.id !== "[") { + warning("Bad operand.", this); + } + this.left = left; + return this; + }; + return x; + } + + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + function optionalidentifier(fnparam) { + if (nexttoken.identifier) { + advance(); + if (token.reserved && !option.es5) { + // `undefined` as a function param is a common pattern to protect + // against the case when somebody does `undefined = true` and + // help with minification. More info: https://gist.github.com/315916 + if (!fnparam || token.value !== "undefined") { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + } + return token.value; + } + } + + // fnparam means that this identifier is being defined as a function + // argument + function identifier(fnparam) { + var i = optionalidentifier(fnparam); + if (i) { + return i; + } + if (token.id === "function" && nexttoken.id === "(") { + warning("Missing name in function declaration."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (nexttoken.id !== ";" || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== "(endline)") { + if (t.id === "function") { + if (!option.latedef) { + break; + } + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '{a}' after '{b}'.", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var i = indent, r, s = scope, t = nexttoken; + + if (t.id === ";") { + advance(";"); + return; + } + + // Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ":") { + advance(); + advance(":"); + scope = Object.create(s); + addlabel(t.value, "label"); + + if (!nexttoken.labelled && nexttoken.value !== "{") { + warning("Label '{a}' on {b} statement.", nexttoken, t.value, nexttoken.value); + } + + if (jx.test(t.value + ":")) { + warning("Label '{a}' looks like a javascript url.", t, t.value); + } + + nexttoken.label = t.value; + t = nexttoken; + } + + // Is it a lonely block? + + if (t.id === "{") { + block(true, true); + return; + } + + // Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + // Look for the final semicolon. + + if (!t.block) { + if (!option.expr && (!r || !r.exps)) { + warning("Expected an assignment or function call and instead saw an expression.", + token); + } else if (option.nonew && r.id === "(" && r.left.id === "new") { + warning("Do not use 'new' for side effects.", t); + } + + if (nexttoken.id === ",") { + return comma(); + } + + if (nexttoken.id !== ";") { + if (!option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if (!option.lastsemic || nexttoken.id !== "}" || + nexttoken.line !== token.line) { + warningAt("Missing semicolon.", token.line, token.character); + } + } + } else { + adjacent(token, nexttoken); + advance(";"); + nonadjacent(token, nexttoken); + } + } + +// Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function statements(startLine) { + var a = [], p; + + while (!nexttoken.reach && nexttoken.id !== "(end)") { + if (nexttoken.id === ";") { + p = peek(); + if (!p || p.id !== "(") { + warning("Unnecessary semicolon."); + } + advance(";"); + } else { + a.push(statement(startLine === nexttoken.line)); + } + } + return a; + } + + + /* + * read all directives + * recognizes a simple form of asi, but always + * warns, if it is used + */ + function directives() { + var i, p, pn; + + for (;;) { + if (nexttoken.id === "(string)") { + p = peek(0); + if (p.id === "(endline)") { + i = 1; + do { + pn = peek(i); + i = i + 1; + } while (pn.id === "(endline)"); + + if (pn.id !== ";") { + if (pn.id !== "(string)" && pn.id !== "(number)" && + pn.id !== "(regexp)" && pn.identifier !== true && + pn.id !== "}") { + break; + } + warning("Missing semicolon.", nexttoken); + } else { + p = pn; + } + } else if (p.id === "}") { + // directive with no other statements, warn about missing semicolon + warning("Missing semicolon.", p); + } else if (p.id !== ";") { + break; + } + + indentation(); + advance(); + if (directive[token.value]) { + warning("Unnecessary directive \"{a}\".", token, token.value); + } + + if (token.value === "use strict") { + if (!option["(explicitNewcap)"]) + option.newcap = true; + option.undef = true; + } + + // there's no directive negation, so always set to true + directive[token.value] = true; + + if (p.id === ";") { + advance(";"); + } + continue; + } + break; + } + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + * isfunc - true if block is a function body + */ + function block(ordinary, stmt, isfunc) { + var a, + b = inblock, + old_indent = indent, + m, + s = scope, + t, + line, + d; + + inblock = ordinary; + + if (!ordinary || !option.funcscope) + scope = Object.create(scope); + + nonadjacent(token, nexttoken); + t = nexttoken; + + var metrics = funct["(metrics)"]; + metrics.nestedBlockDepth += 1; + metrics.verifyMaxNestedBlockDepthPerFunction(); + + if (nexttoken.id === "{") { + advance("{"); + line = token.line; + if (nexttoken.id !== "}") { + indent += option.indent; + while (!ordinary && nexttoken.from > indent) { + indent += option.indent; + } + + if (isfunc) { + m = {}; + for (d in directive) { + if (is_own(directive, d)) { + m[d] = directive[d]; + } + } + directives(); + + if (option.strict && funct["(context)"]["(global)"]) { + if (!m["use strict"] && !directive["use strict"]) { + warning("Missing \"use strict\" statement."); + } + } + } + + a = statements(line); + + metrics.statementCount += a.length; + + if (isfunc) { + directive = m; + } + + indent -= option.indent; + if (line !== nexttoken.line) { + indentation(); + } + } else if (line !== nexttoken.line) { + indentation(); + } + advance("}", t); + indent = old_indent; + } else if (!ordinary) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, "{", nexttoken.value); + } else { + if (!stmt || option.curly) + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, "{", nexttoken.value); + + noreach = true; + indent += option.indent; + // test indentation only if statement is in new line + a = [statement(nexttoken.line === token.line)]; + indent -= option.indent; + noreach = false; + } + funct["(verb)"] = null; + if (!ordinary || !option.funcscope) scope = s; + inblock = b; + if (ordinary && option.noempty && (!a || a.length === 0)) { + warning("Empty block."); + } + metrics.nestedBlockDepth -= 1; + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== "boolean") { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === "number") { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === "function") { + a = false; + } + + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + + // Build the syntax table by declaring the syntactic elements of the language. + + type("(number)", function () { + return this; + }); + + type("(string)", function () { + return this; + }); + + syntax["(identifier)"] = { + type: "(identifier)", + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + + if (typeof s === "function") { + // Protection against accidental inheritance. + s = undefined; + } else if (typeof s === "boolean") { + f = funct; + funct = functions[0]; + addlabel(v, "var"); + s = funct; + funct = f; + } + + // The name is in scope and defined in the current function. + if (funct === s) { + // Change 'unused' to 'var', and reject labels. + switch (funct[v]) { + case "unused": + funct[v] = "var"; + break; + case "unction": + funct[v] = "function"; + this["function"] = true; + break; + case "function": + this["function"] = true; + break; + case "label": + warning("'{a}' is a statement label.", token, v); + break; + } + } else if (funct["(global)"]) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + + if (option.undef && typeof predefined[v] !== "boolean") { + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if (!(anonname === "typeof" || anonname === "delete") || + (nexttoken && (nexttoken.value === "." || nexttoken.value === "["))) { + + isundef(funct, "'{a}' is not defined.", token, v); + } + } + + note_implied(token); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case "closure": + case "function": + case "var": + case "unused": + warning("'{a}' used out of scope.", token, v); + break; + case "label": + warning("'{a}' is a statement label.", token, v); + break; + case "outer": + case "global": + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== "object") { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // display warning if we're inside of typeof or delete. + if (option.undef) { + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if (!(anonname === "typeof" || anonname === "delete") || + (nexttoken && + (nexttoken.value === "." || nexttoken.value === "["))) { + + isundef(funct, "'{a}' is not defined.", token, v); + } + } + funct[v] = true; + note_implied(token); + } else { + switch (s[v]) { + case "function": + case "unction": + this["function"] = true; + s[v] = "closure"; + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "var": + case "unused": + s[v] = "closure"; + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "closure": + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "label": + warning("'{a}' is a statement label.", token, v); + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type("(regexp)", function () { + return this; + }); + + +// ECMAScript parser + + delim("(endline)"); + delim("(begin)"); + delim("(end)").reach = true; + delim(""); + delim("(error)").reach = true; + delim("}").reach = true; + delim(")"); + delim("]"); + delim("\"").reach = true; + delim("'").reach = true; + delim(";"); + delim(":").reach = true; + delim(","); + delim("#"); + delim("@"); + reserve("else"); + reserve("case").reach = true; + reserve("catch"); + reserve("default").reach = true; + reserve("finally"); + reservevar("arguments", function (x) { + if (directive["use strict"] && funct["(global)"]) { + warning("Strict violation.", x); + } + }); + reservevar("eval"); + reservevar("false"); + reservevar("Infinity"); + reservevar("null"); + reservevar("this", function (x) { + if (directive["use strict"] && !option.validthis && ((funct["(statement)"] && + funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { + warning("Possible strict violation.", x); + } + }); + reservevar("true"); + reservevar("undefined"); + assignop("=", "assign", 20); + assignop("+=", "assignadd", 20); + assignop("-=", "assignsub", 20); + assignop("*=", "assignmult", 20); + assignop("/=", "assigndiv", 20).nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop("%=", "assignmod", 20); + bitwiseassignop("&=", "assignbitand", 20); + bitwiseassignop("|=", "assignbitor", 20); + bitwiseassignop("^=", "assignbitxor", 20); + bitwiseassignop("<<=", "assignshiftleft", 20); + bitwiseassignop(">>=", "assignshiftright", 20); + bitwiseassignop(">>>=", "assignshiftrightunsigned", 20); + infix("?", function (left, that) { + that.left = left; + that.right = expression(10); + advance(":"); + that["else"] = expression(10); + return that; + }, 30); + + infix("||", "or", 40); + infix("&&", "and", 50); + bitwise("|", "bitor", 70); + bitwise("^", "bitxor", 80); + bitwise("&", "bitand", 90); + relation("==", function (left, right) { + var eqnull = option.eqnull && (left.value === "null" || right.value === "null"); + + if (!eqnull && option.eqeqeq) + warning("Expected '{a}' and instead saw '{b}'.", this, "===", "=="); + else if (isPoorRelation(left)) + warning("Use '{a}' to compare with '{b}'.", this, "===", left.value); + else if (isPoorRelation(right)) + warning("Use '{a}' to compare with '{b}'.", this, "===", right.value); + + return this; + }); + relation("==="); + relation("!=", function (left, right) { + var eqnull = option.eqnull && + (left.value === "null" || right.value === "null"); + + if (!eqnull && option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, "!==", "!="); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, "!==", left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, "!==", right.value); + } + return this; + }); + relation("!=="); + relation("<"); + relation(">"); + relation("<="); + relation(">="); + bitwise("<<", "shiftleft", 120); + bitwise(">>", "shiftright", 120); + bitwise(">>>", "shiftrightunsigned", 120); + infix("in", "in", 120); + infix("instanceof", "instanceof", 120); + infix("+", function (left, that) { + var right = expression(130); + if (left && right && left.id === "(string)" && right.id === "(string)") { + left.value += right.value; + left.character = right.character; + if (!option.scripturl && jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix("+", "num"); + prefix("+++", function () { + warning("Confusing pluses."); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix("+++", function (left) { + warning("Confusing pluses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix("-", "sub", 130); + prefix("-", "neg"); + prefix("---", function () { + warning("Confusing minuses."); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix("---", function (left) { + warning("Confusing minuses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix("*", "mult", 140); + infix("/", "div", 140); + infix("%", "mod", 140); + + suffix("++", "postinc"); + prefix("++", "preinc"); + syntax["++"].exps = true; + + suffix("--", "postdec"); + prefix("--", "predec"); + syntax["--"].exps = true; + prefix("delete", function () { + var p = expression(0); + if (!p || (p.id !== "." && p.id !== "[")) { + warning("Variables should not be deleted."); + } + this.first = p; + return this; + }).exps = true; + + prefix("~", function () { + if (option.bitwise) { + warning("Unexpected '{a}'.", this, "~"); + } + expression(150); + return this; + }); + + prefix("!", function () { + this.right = expression(150); + this.arity = "unary"; + if (bang[this.right.id] === true) { + warning("Confusing use of '{a}'.", this, "!"); + } + return this; + }); + prefix("typeof", "typeof"); + prefix("new", function () { + var c = expression(155), i; + if (c && c.id !== "function") { + if (c.identifier) { + c["new"] = true; + switch (c.value) { + case "Number": + case "String": + case "Boolean": + case "Math": + case "JSON": + warning("Do not use {a} as a constructor.", prevtoken, c.value); + break; + case "Function": + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case "Date": + case "RegExp": + break; + default: + if (c.id !== "function") { + i = c.value.substr(0, 1); + if (option.newcap && (i < "A" || i > "Z") && !is_own(global, c.value)) { + warning("A constructor name should start with an uppercase letter.", + token); + } + } + } + } else { + if (c.id !== "." && c.id !== "[" && c.id !== "(") { + warning("Bad constructor.", token); + } + } + } else { + if (!option.supernew) + warning("Weird construction. Delete 'new'.", this); + } + adjacent(token, nexttoken); + if (nexttoken.id !== "(" && !option.supernew) { + warning("Missing '()' invoking a constructor.", + token, token.value); + } + this.first = c; + return this; + }); + syntax["new"].exps = true; + + prefix("void").exps = true; + + infix(".", function (left, that) { + adjacent(prevtoken, token); + nobreak(); + var m = identifier(); + if (typeof m === "string") { + countMember(m); + } + that.left = left; + that.right = m; + if (left && left.value === "arguments" && (m === "callee" || m === "caller")) { + if (option.noarg) + warning("Avoid arguments.{a}.", left, m); + else if (directive["use strict"]) + error("Strict violation."); + } else if (!option.evil && left && left.value === "document" && + (m === "write" || m === "writeln")) { + warning("document.write can be a form of eval.", left); + } + if (!option.evil && (m === "eval" || m === "execScript")) { + warning("eval is evil."); + } + return that; + }, 160, true); + + infix("(", function (left, that) { + if (prevtoken.id !== "}" && prevtoken.id !== ")") { + nobreak(prevtoken, token); + } + nospace(); + if (option.immed && !left.immed && left.id === "function") { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var n = 0, + p = []; + if (left) { + if (left.type === "(identifier)") { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if ("Number String Boolean Date Object".indexOf(left.value) === -1) { + if (left.value === "Math") { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning("Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } + } + if (nexttoken.id !== ")") { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (nexttoken.id !== ",") { + break; + } + comma(); + } + } + advance(")"); + nospace(prevtoken, token); + if (typeof left === "object") { + if (left.value === "parseInt" && n === 1) { + warning("Missing radix parameter.", token); + } + if (!option.evil) { + if (left.value === "eval" || left.value === "Function" || + left.value === "execScript") { + warning("eval is evil.", left); + + if (p[0] && [0].id === "(string)") { + addInternalSrc(left, p[0].value); + } + } else if (p[0] && p[0].id === "(string)" && + (left.value === "setTimeout" || + left.value === "setInterval")) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + addInternalSrc(left, p[0].value); + + // window.setTimeout/setInterval + } else if (p[0] && p[0].id === "(string)" && + left.value === "." && + left.left.value === "window" && + (left.right === "setTimeout" || + left.right === "setInterval")) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + addInternalSrc(left, p[0].value); + } + } + if (!left.identifier && left.id !== "." && left.id !== "[" && + left.id !== "(" && left.id !== "&&" && left.id !== "||" && + left.id !== "?") { + warning("Bad invocation.", left); + } + } + that.left = left; + return that; + }, 155, true).exps = true; + + prefix("(", function () { + nospace(); + if (nexttoken.id === "function") { + nexttoken.immed = true; + } + var v = expression(0); + advance(")", this); + nospace(prevtoken, token); + if (option.immed && v.id === "function") { + if (nexttoken.id !== "(" && + (nexttoken.id !== "." || (peek().value !== "call" && peek().value !== "apply"))) { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + + return v; + }); + + infix("[", function (left, that) { + nobreak(prevtoken, token); + nospace(); + var e = expression(0), s; + if (e && e.type === "(string)") { + if (!option.evil && (e.value === "eval" || e.value === "execScript")) { + warning("eval is evil.", that); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + prevtoken, e.value); + } + } + } + advance("]", that); + nospace(prevtoken, token); + that.left = left; + that.right = e; + return that; + }, 160, true); + + prefix("[", function () { + var b = token.line !== nexttoken.line; + this.first = []; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + while (nexttoken.id !== "(end)") { + while (nexttoken.id === ",") { + if (!option.es5) + warning("Extra comma."); + advance(","); + } + if (nexttoken.id === "]") { + break; + } + if (b && token.line !== nexttoken.line) { + indentation(); + } + this.first.push(expression(10)); + if (nexttoken.id === ",") { + comma(); + if (nexttoken.id === "]" && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance("]", this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === "(string)") { + id = nexttoken.value; + advance(); + } else if (nexttoken.id === "(number)") { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var next = nexttoken; + var params = []; + var ident; + + advance("("); + nospace(); + + if (nexttoken.id === ")") { + advance(")"); + return; + } + + for (;;) { + ident = identifier(true); + params.push(ident); + addlabel(ident, "unused", token); + if (nexttoken.id === ",") { + comma(); + } else { + advance(")", next); + nospace(prevtoken, token); + return params; + } + } + } + + + function doFunction(name, statement) { + var f; + var oldOption = option; + var oldScope = scope; + + option = Object.create(option); + scope = Object.create(scope); + + funct = { + "(name)" : name || "\"" + anonname + "\"", + "(line)" : nexttoken.line, + "(character)": nexttoken.character, + "(context)" : funct, + "(breakage)" : 0, + "(loopage)" : 0, + "(metrics)" : createMetrics(nexttoken), + "(scope)" : scope, + "(statement)": statement, + "(tokens)" : {} + }; + + f = funct; + token.funct = funct; + + functions.push(funct); + + if (name) { + addlabel(name, "function"); + } + + funct["(params)"] = functionparams(); + funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); + + block(false, false, true); + + funct["(metrics)"].verifyMaxStatementsPerFunction(); + funct["(metrics)"].verifyMaxComplexityPerFunction(); + + scope = oldScope; + option = oldOption; + funct["(last)"] = token.line; + funct["(lastcharacter)"] = token.character; + funct = funct["(context)"]; + + return f; + } + + function createMetrics(functionStartToken) { + return { + statementCount: 0, + nestedBlockDepth: -1, + ComplexityCount: 1, + verifyMaxStatementsPerFunction: function () { + if (option.maxstatements && + this.statementCount > option.maxstatements) { + var message = "Too many statements per function (" + this.statementCount + ")."; + warning(message, functionStartToken); + } + }, + + verifyMaxParametersPerFunction: function (params) { + params = params || []; + + if (option.maxparams && params.length > option.maxparams) { + var message = "Too many parameters per function (" + params.length + ")."; + warning(message, functionStartToken); + } + }, + + verifyMaxNestedBlockDepthPerFunction: function () { + if (option.maxdepth && + this.nestedBlockDepth > 0 && + this.nestedBlockDepth === option.maxdepth + 1) { + var message = "Blocks are nested too deeply (" + this.nestedBlockDepth + ")."; + warning(message); + } + }, + + verifyMaxComplexityPerFunction: function () { + var max = option.maxcomplexity; + var cc = this.ComplexityCount; + if (max && cc > max) { + var message = "Cyclomatic complexity is too high per function (" + cc + ")."; + warning(message, functionStartToken); + } + } + }; + } + + function increaseComplexityCount() { + funct["(metrics)"].ComplexityCount += 1; + } + + + (function (x) { + x.nud = function () { + var b, f, i, p, t; + var props = {}; // All properties, including accessors + + function saveProperty(name, token) { + if (props[name] && is_own(props, name)) + warning("Duplicate member '{a}'.", nexttoken, i); + else + props[name] = {}; + + props[name].basic = true; + props[name].basicToken = token; + } + + function saveSetter(name, token) { + if (props[name] && is_own(props, name)) { + if (props[name].basic || props[name].setter) + warning("Duplicate member '{a}'.", nexttoken, i); + } else { + props[name] = {}; + } + + props[name].setter = true; + props[name].setterToken = token; + } + + function saveGetter(name) { + if (props[name] && is_own(props, name)) { + if (props[name].basic || props[name].getter) + warning("Duplicate member '{a}'.", nexttoken, i); + } else { + props[name] = {}; + } + + props[name].getter = true; + props[name].getterToken = token; + } + + b = token.line !== nexttoken.line; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + for (;;) { + if (nexttoken.id === "}") { + break; + } + if (b) { + indentation(); + } + if (nexttoken.value === "get" && peek().id !== ":") { + advance("get"); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + saveGetter(i); + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + p = f["(params)"]; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + adjacent(token, nexttoken); + } else if (nexttoken.value === "set" && peek().id !== ":") { + advance("set"); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + saveSetter(i, nexttoken); + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + p = f["(params)"]; + if (!p || p.length !== 1) { + warning("Expected a single parameter in set {a} function.", t, i); + } + } else { + i = property_name(); + saveProperty(i, nexttoken); + if (typeof i !== "string") { + break; + } + advance(":"); + nonadjacent(token, nexttoken); + expression(10); + } + + countMember(i); + if (nexttoken.id === ",") { + comma(); + if (nexttoken.id === ",") { + warning("Extra comma.", token); + } else if (nexttoken.id === "}" && !option.es5) { + warning("Extra comma.", token); + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance("}", this); + + // Check for lonely setters if in the ES5 mode. + if (option.es5) { + for (var name in props) { + if (is_own(props, name) && props[name].setter && !props[name].getter) { + warning("Setter is defined without getter.", props[name].setterToken); + } + } + } + return this; + }; + x.fud = function () { + error("Expected to see a statement and instead saw a block.", token); + }; + }(delim("{"))); + +// This Function is called when esnext option is set to true +// it adds the `const` statement to JSHINT + + useESNextSyntax = function () { + var conststatement = stmt("const", function (prefix) { + var id, name, value; + + this.first = []; + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + if (funct[id] === "const") { + warning("const '" + id + "' has already been declared"); + } + if (funct["(global)"] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, "const"); + if (prefix) { + break; + } + name = token; + this.first.push(token); + + if (nexttoken.id !== "=") { + warning("const " + + "'{a}' is initialized to 'undefined'.", token, id); + } + + if (nexttoken.id === "=") { + nonadjacent(token, nexttoken); + advance("="); + nonadjacent(token, nexttoken); + if (nexttoken.id === "undefined") { + warning("It is not necessary to initialize " + + "'{a}' to 'undefined'.", token, id); + } + if (peek(0).id === "=" && nexttoken.identifier) { + error("Constant {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = expression(0); + name.first = value; + } + + if (nexttoken.id !== ",") { + break; + } + comma(); + } + return this; + }); + conststatement.exps = true; + }; + + var varstatement = stmt("var", function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var id, name, value; + + if (funct["(onevar)"] && option.onevar) { + warning("Too many var statements."); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + + if (option.esnext && funct[id] === "const") { + warning("const '" + id + "' has already been declared"); + } + + if (funct["(global)"] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + + addlabel(id, "unused", token); + + if (prefix) { + break; + } + + name = token; + this.first.push(token); + + if (nexttoken.id === "=") { + nonadjacent(token, nexttoken); + advance("="); + nonadjacent(token, nexttoken); + if (nexttoken.id === "undefined") { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === "=" && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = expression(0); + name.first = value; + } + if (nexttoken.id !== ",") { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + + blockstmt("function", function () { + if (inblock) { + warning("Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", token); + + } + var i = identifier(); + if (option.esnext && funct[i] === "const") { + warning("const '" + i + "' has already been declared"); + } + adjacent(token, nexttoken); + addlabel(i, "unction", token); + + doFunction(i, { statement: true }); + if (nexttoken.id === "(" && nexttoken.line === token.line) { + error( +"Function declarations are not invocable. Wrap the whole function invocation in parens."); + } + return this; + }); + + prefix("function", function () { + var i = optionalidentifier(); + if (i) { + adjacent(token, nexttoken); + } else { + nonadjacent(token, nexttoken); + } + doFunction(i); + if (!option.loopfunc && funct["(loopage)"]) { + warning("Don't make functions within a loop."); + } + return this; + }); + + blockstmt("if", function () { + var t = nexttoken; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === "=") { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance("="); + expression(20); + } + advance(")", t); + nospace(prevtoken, token); + block(true, true); + if (nexttoken.id === "else") { + nonadjacent(token, nexttoken); + advance("else"); + if (nexttoken.id === "if" || nexttoken.id === "switch") { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt("try", function () { + var b; + + function doCatch() { + var oldScope = scope; + var e; + + advance("catch"); + nonadjacent(token, nexttoken); + advance("("); + + scope = Object.create(oldScope); + + e = nexttoken.value; + if (nexttoken.type !== "(identifier)") { + e = null; + warning("Expected an identifier and instead saw '{a}'.", nexttoken, e); + } + + advance(); + advance(")"); + + funct = { + "(name)" : "(catch)", + "(line)" : nexttoken.line, + "(character)": nexttoken.character, + "(context)" : funct, + "(breakage)" : funct["(breakage)"], + "(loopage)" : funct["(loopage)"], + "(scope)" : scope, + "(statement)": false, + "(metrics)" : createMetrics(nexttoken), + "(catch)" : true, + "(tokens)" : {} + }; + + if (e) { + addlabel(e, "exception"); + } + + token.funct = funct; + functions.push(funct); + + block(false); + + scope = oldScope; + + funct["(last)"] = token.line; + funct["(lastcharacter)"] = token.character; + funct = funct["(context)"]; + } + + block(false); + + if (nexttoken.id === "catch") { + increaseComplexityCount(); + doCatch(); + b = true; + } + + if (nexttoken.id === "finally") { + advance("finally"); + block(false); + return; + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, "catch", nexttoken.value); + } + + return this; + }); + + blockstmt("while", function () { + var t = nexttoken; + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === "=") { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance("="); + expression(20); + } + advance(")", t); + nospace(prevtoken, token); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }).labelled = true; + + blockstmt("with", function () { + var t = nexttoken; + if (directive["use strict"]) { + error("'with' is not allowed in strict mode.", token); + } else if (!option.withstmt) { + warning("Don't use 'with'.", token); + } + + advance("("); + nonadjacent(this, t); + nospace(); + expression(0); + advance(")", t); + nospace(prevtoken, token); + block(true, true); + + return this; + }); + + blockstmt("switch", function () { + var t = nexttoken, + g = false; + funct["(breakage)"] += 1; + advance("("); + nonadjacent(this, t); + nospace(); + this.condition = expression(20); + advance(")", t); + nospace(prevtoken, token); + nonadjacent(token, nexttoken); + t = nexttoken; + advance("{"); + nonadjacent(token, nexttoken); + indent += option.indent; + this.cases = []; + for (;;) { + switch (nexttoken.id) { + case "case": + switch (funct["(verb)"]) { + case "break": + case "case": + case "continue": + case "return": + case "switch": + case "throw": + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'case'.", + token); + } + } + indentation(-option.indent); + advance("case"); + this.cases.push(expression(20)); + increaseComplexityCount(); + g = true; + advance(":"); + funct["(verb)"] = "case"; + break; + case "default": + switch (funct["(verb)"]) { + case "break": + case "continue": + case "return": + case "throw": + break; + default: + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'default'.", + token); + } + } + indentation(-option.indent); + advance("default"); + g = true; + advance(":"); + break; + case "}": + indent -= option.indent; + indentation(); + advance("}", t); + if (this.cases.length === 1 || this.condition.id === "true" || + this.condition.id === "false") { + if (!option.onecase) + warning("This 'switch' should be an 'if'.", this); + } + funct["(breakage)"] -= 1; + funct["(verb)"] = undefined; + return; + case "(end)": + error("Missing '{a}'.", nexttoken, "}"); + return; + default: + if (g) { + switch (token.id) { + case ",": + error("Each value should have its own case label."); + return; + case ":": + g = false; + statements(); + break; + default: + error("Missing ':' on a case clause.", token); + return; + } + } else { + if (token.id === ":") { + advance(":"); + error("Unexpected '{a}'.", token, ":"); + statements(); + } else { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, "case", nexttoken.value); + return; + } + } + } + } + }).labelled = true; + + stmt("debugger", function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + return this; + }).exps = true; + + (function () { + var x = stmt("do", function () { + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + + this.first = block(true); + advance("while"); + var t = nexttoken; + nonadjacent(token, t); + advance("("); + nospace(); + expression(20); + if (nexttoken.id === "=") { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance("="); + expression(20); + } + advance(")", t); + nospace(prevtoken, token); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt("for", function () { + var s, t = nexttoken; + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + if (peek(nexttoken.id === "var" ? 1 : 0).id === "in") { + if (nexttoken.id === "var") { + advance("var"); + varstatement.fud.call(varstatement, true); + } else { + switch (funct[nexttoken.value]) { + case "unused": + funct[nexttoken.value] = "var"; + break; + case "var": + break; + default: + warning("Bad for in variable '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance("in"); + expression(20); + advance(")", t); + s = block(true, true); + if (option.forin && s && (s.length > 1 || typeof s[0] !== "object" || + s[0].value !== "if")) { + warning("The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", this); + } + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + } else { + if (nexttoken.id !== ";") { + if (nexttoken.id === "var") { + advance("var"); + varstatement.fud.call(varstatement); + } else { + for (;;) { + expression(0, "for"); + if (nexttoken.id !== ",") { + break; + } + comma(); + } + } + } + nolinebreak(token); + advance(";"); + if (nexttoken.id !== ";") { + expression(20); + if (nexttoken.id === "=") { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance("="); + expression(20); + } + } + nolinebreak(token); + advance(";"); + if (nexttoken.id === ";") { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, ")", ";"); + } + if (nexttoken.id !== ")") { + for (;;) { + expression(0, "for"); + if (nexttoken.id !== ",") { + break; + } + comma(); + } + } + advance(")", t); + nospace(prevtoken, token); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + } + }).labelled = true; + + + stmt("break", function () { + var v = nexttoken.value; + + if (funct["(breakage)"] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ";") { + if (token.line === nexttoken.line) { + if (funct[v] !== "label") { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } + reachable("break"); + return this; + }).exps = true; + + + stmt("continue", function () { + var v = nexttoken.value; + + if (funct["(breakage)"] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ";") { + if (token.line === nexttoken.line) { + if (funct[v] !== "label") { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } else if (!funct["(loopage)"]) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + reachable("continue"); + return this; + }).exps = true; + + + stmt("return", function () { + if (this.line === nexttoken.line) { + if (nexttoken.id === "(regexp)") + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + + if (nexttoken.id !== ";" && !nexttoken.reach) { + nonadjacent(token, nexttoken); + if (peek().value === "=" && !option.boss) { + warningAt("Did you mean to return a conditional instead of an assignment?", + token.line, token.character + 1); + } + this.first = expression(0); + } + } else if (!option.asi) { + nolinebreak(this); // always warn (Line breaking error) + } + reachable("return"); + return this; + }).exps = true; + + + stmt("throw", function () { + nolinebreak(this); + nonadjacent(token, nexttoken); + this.first = expression(20); + reachable("throw"); + return this; + }).exps = true; + +// Superfluous reserved words + + reserve("class"); + reserve("const"); + reserve("enum"); + reserve("export"); + reserve("extends"); + reserve("import"); + reserve("super"); + + reserve("let"); + reserve("yield"); + reserve("implements"); + reserve("interface"); + reserve("package"); + reserve("private"); + reserve("protected"); + reserve("public"); + reserve("static"); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance("{"); + if (nexttoken.id !== "}") { + for (;;) { + if (nexttoken.id === "(end)") { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === "}") { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ",") { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== "(string)") { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if ((nexttoken.value === "__proto__" && + !option.proto) || (nexttoken.value === "__iterator__" && + !option.iterator)) { + warning("The '{a}' key may produce unexpected results.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(":"); + jsonValue(); + if (nexttoken.id !== ",") { + break; + } + advance(","); + } + } + advance("}"); + } + + function jsonArray() { + var t = nexttoken; + advance("["); + if (nexttoken.id !== "]") { + for (;;) { + if (nexttoken.id === "(end)") { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === "]") { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ",") { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ",") { + break; + } + advance(","); + } + } + advance("]"); + } + + switch (nexttoken.id) { + case "{": + jsonObject(); + break; + case "[": + jsonArray(); + break; + case "true": + case "false": + case "null": + case "(number)": + case "(string)": + advance(); + break; + case "-": + advance("-"); + if (token.character !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + adjacent(token, nexttoken); + advance("(number)"); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + + // The actual JSHINT function itself. + var itself = function (s, o, g) { + var a, i, k, x; + var optionKeys; + var newOptionObj = {}; + + if (o && o.scope) { + JSHINT.scope = o.scope; + } else { + JSHINT.errors = []; + JSHINT.undefs = []; + JSHINT.internals = []; + JSHINT.blacklist = {}; + JSHINT.scope = "(main)"; + } + + predefined = Object.create(standard); + declared = Object.create(null); + combine(predefined, g || {}); + + if (o) { + a = o.predef; + if (a) { + if (!Array.isArray(a) && typeof a === "object") { + a = Object.keys(a); + } + a.forEach(function (item) { + var slice; + if (item[0] === "-") { + slice = item.slice(1); + JSHINT.blacklist[slice] = slice; + } else { + predefined[item] = true; + } + }); + } + + optionKeys = Object.keys(o); + for (x = 0; x < optionKeys.length; x++) { + newOptionObj[optionKeys[x]] = o[optionKeys[x]]; + + if (optionKeys[x] === "newcap" && o[optionKeys[x]] === false) + newOptionObj["(explicitNewcap)"] = true; + + if (optionKeys[x] === "indent") + newOptionObj.white = true; + } + } + + option = newOptionObj; + + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + + tab = ""; + for (i = 0; i < option.indent; i += 1) { + tab += " "; + } + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + "(global)": true, + "(name)": "(global)", + "(scope)": scope, + "(breakage)": 0, + "(loopage)": 0, + "(tokens)": {}, + "(metrics)": createMetrics(nexttoken) + }; + functions = [funct]; + urls = []; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lines = []; + unuseds = []; + + if (!isString(s) && !Array.isArray(s)) { + errorAt("Input is neither a string nor an array of strings.", 0); + return false; + } + + if (isString(s) && /^\s*$/g.test(s)) { + errorAt("Input is an empty string.", 0); + return false; + } + + if (s.length === 0) { + errorAt("Input is an empty array.", 0); + return false; + } + + lex.init(s); + + prereg = true; + directive = {}; + + prevtoken = token = nexttoken = syntax["(begin)"]; + + // Check options + for (var name in o) { + if (is_own(o, name)) { + checkOption(name, token); + } + } + + assume(); + + // combine the passed globals after we've assumed all our options + combine(predefined, g || {}); + + //reset values + comma.first = true; + quotmark = undefined; + + try { + advance(); + switch (nexttoken.id) { + case "{": + case "[": + option.laxbreak = true; + jsonmode = true; + jsonValue(); + break; + default: + directives(); + if (directive["use strict"] && !option.globalstrict) { + warning("Use the function form of \"use strict\".", prevtoken); + } + + statements(); + } + advance((nexttoken && nexttoken.value !== ".") ? "(end)" : undefined); + + var markDefined = function (name, context) { + do { + if (typeof context[name] === "string") { + // JSHINT marks unused variables as 'unused' and + // unused function declaration as 'unction'. This + // code changes such instances back 'var' and + // 'closure' so that the code in JSHINT.data() + // doesn't think they're unused. + + if (context[name] === "unused") + context[name] = "var"; + else if (context[name] === "unction") + context[name] = "closure"; + + return true; + } + + context = context["(context)"]; + } while (context); + + return false; + }; + + var clearImplied = function (name, line) { + if (!implied[name]) + return; + + var newImplied = []; + for (var i = 0; i < implied[name].length; i += 1) { + if (implied[name][i] !== line) + newImplied.push(implied[name][i]); + } + + if (newImplied.length === 0) + delete implied[name]; + else + implied[name] = newImplied; + }; + + var warnUnused = function (name, token) { + var line = token.line; + var chr = token.character; + + if (option.unused) + warningAt("'{a}' is defined but never used.", line, chr, name); + + unuseds.push({ + name: name, + line: line, + character: chr + }); + }; + + var checkUnused = function (func, key) { + var type = func[key]; + var token = func["(tokens)"][key]; + + if (key.charAt(0) === "(") + return; + + if (type !== "unused" && type !== "unction") + return; + + // Params are checked separately from other variables. + if (func["(params)"] && func["(params)"].indexOf(key) !== -1) + return; + + warnUnused(key, token); + }; + + // Check queued 'x is not defined' instances to see if they're still undefined. + for (i = 0; i < JSHINT.undefs.length; i += 1) { + k = JSHINT.undefs[i].slice(0); + + if (markDefined(k[2].value, k[0])) { + clearImplied(k[2].value, k[2].line); + } else { + warning.apply(warning, k.slice(1)); + } + } + + functions.forEach(function (func) { + for (var key in func) { + if (is_own(func, key)) { + checkUnused(func, key); + } + } + + if (!func["(params)"]) + return; + + var params = func["(params)"].slice(); + var param = params.pop(); + var type; + + while (param) { + type = func[param]; + + // 'undefined' is a special case for (function (window, undefined) { ... })(); + // patterns. + + if (param === "undefined") + return; + + if (type !== "unused" && type !== "unction") + return; + + warnUnused(param, func["(tokens)"][param]); + param = params.pop(); + } + }); + + for (var key in declared) { + if (is_own(declared, key) && !is_own(global, key)) { + warnUnused(key, declared[key]); + } + } + } catch (e) { + if (e) { + var nt = nexttoken || {}; + JSHINT.errors.push({ + raw : e.raw, + reason : e.message, + line : e.line || nt.line, + character : e.character || nt.from + }, null); + } + } + + // Loop over the listed "internals", and check them as well. + + if (JSHINT.scope === "(main)") { + o = o || {}; + + for (i = 0; i < JSHINT.internals.length; i += 1) { + k = JSHINT.internals[i]; + o.scope = k.elem; + itself(k.value, o, g); + } + } + + return JSHINT.errors.length === 0; + }; + + // Data summary. + itself.data = function () { + var data = { + functions: [], + options: option + }; + var implieds = []; + var members = []; + var fu, f, i, j, n, globals; + + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + + fu.name = f["(name)"]; + fu.param = f["(params)"]; + fu.line = f["(line)"]; + fu.character = f["(character)"]; + fu.last = f["(last)"]; + fu.lastcharacter = f["(lastcharacter)"]; + data.functions.push(fu); + } + + if (unuseds.length > 0) { + data.unused = unuseds; + } + + members = []; + for (n in member) { + if (typeof member[n] === "number") { + data.member = member; + break; + } + } + + return data; + }; + + itself.jshint = itself; + + return itself; +}()); + +// Make JSHINT a Node module, if possible. +if (typeof exports === "object" && exports) { + exports.JSHINT = JSHINT; +} From 895c5096ef0d4a36d2bda13db3e2d42c8dbd7580 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 15:28:55 -0700 Subject: [PATCH 026/931] Change to navigation Unless there is an explicit location to navigate to, always try to navigate to the history item. --- .../js-tests/navHIstory/navHistoryTests.js | 20 ++++++++++ client/scripts/scripted/utils/navHistory.js | 40 ++++++++++--------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/client/scripts/js-tests/navHIstory/navHistoryTests.js b/client/scripts/js-tests/navHIstory/navHistoryTests.js index 4c59be58..5100f836 100644 --- a/client/scripts/js-tests/navHIstory/navHistoryTests.js +++ b/client/scripts/js-tests/navHIstory/navHistoryTests.js @@ -311,6 +311,26 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio assert.deepEqual(window.subeditors[0].getSelection(), {start:20,end:30}); }; + tests.testNavigateUsingImplicitHistory = function() { + setup(); + + // initial selection should be 0 + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + assert.deepEqual(window.editor.getSelection(), {start:0,end:0}); + + // explicit set of selection through url + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#40,50" }); + assert.deepEqual(window.editor.getSelection(), {start:40,end:50}); + + // go to a new file + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js" }); + assert.deepEqual(window.editor.getSelection(), {start:0,end:0}); + + // back to original file and ensure selection is grabbed from history + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); + assert.deepEqual(window.editor.getSelection(), {start:40,end:50}); + } + // still to test // raw history object diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index 48472b99..d75ad0cb 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -57,7 +57,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD $('#side_panel').trigger('open'); }; - var scrollDefinition = function(editor) { + var scrollToSelection = function(editor) { var tv = editor.getTextView(); var model = tv.getModel(); var offset = tv.getCaretOffset(); @@ -80,6 +80,15 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD return JSON.parse(historyJSON); }; + var getHistoryAsObject = function() { + var histArr = getHistory(); + var histObj = {}; + for (var i = 0; i < histArr.length; i++) { + histObj[histArr[i].filepath] = histArr[i]; + } + return histObj; + }; + var setHistory = function(history) { localStorage.setItem("scriptedHistory", JSON.stringify(history)); }; @@ -179,6 +188,13 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } filepath = filepath.substring(0, hashIndex); } + if (!range) { + // try to get range from history + var histItem = getHistoryAsObject()[filepath]; + if (histItem) { + range = histItem.range; + } + } if (isBinary(filepath)) { alert("Cannot open binary files"); @@ -241,23 +257,8 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } }; - - // don't think we need this any more -// var highlightSelection = function(editor) { -// try{ -// var loc = JSON.parse("[" + location.hash.substring(1) + "]"); -// if (loc && loc.constructor === Array && loc.length > 0) { -// var start = loc[0]; -// var end = loc.length > 1 ? loc[1] : start; -// editor.getTextView().setSelection(start, end, true); -// scrollDefinition(editor); -// } -// } catch (e) { -// console.log("Could not navigate to location specified in hash. Hash value: " + location.hash); -// } -// }; - /** + * Opens the given editor on the given definition * @param {{String|Object}} modifier either EDITOR_TARGET.main, EDITOR_TARGET.sub, or EDITOR_TARGET.tab * @param {{range:List.,path:String}} definition * @param {{Editor}} editor @@ -293,6 +294,9 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } } + /** + * Adds one-time configuration to the main editor + */ var buildMaineditor = function() { $('#editor').click(function(event) { openOnClick(event, window.editor); @@ -730,7 +734,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD console.log(range); } targetEditor.getTextView().setSelection(range[0], range[1], true); - scrollDefinition(targetEditor); + scrollToSelection(targetEditor); } if (target === EDITOR_TARGET.main) { From cc658739891782a566f8a549c998de5d2d97e17d Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Fri, 26 Oct 2012 16:33:56 -0700 Subject: [PATCH 027/931] Initial drop of jshint integration It is now possible to choose jshint as the linter via .scripted using editor.linter = "jshint". Issue: SCRIPTED-220 --- .../scripts/scripted/editor/jshintdriver.js | 150 ++++++++++++++++++ .../scripts/scripted/editor/scriptedEditor.js | 12 +- 2 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 client/scripts/scripted/editor/jshintdriver.js diff --git a/client/scripts/scripted/editor/jshintdriver.js b/client/scripts/scripted/editor/jshintdriver.js new file mode 100644 index 00000000..bccb8213 --- /dev/null +++ b/client/scripts/scripted/editor/jshintdriver.js @@ -0,0 +1,150 @@ +/******************************************************************************* + * @license + * Copyright (c) 2012 VMware and others + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * Andy Clement - based on jslintdriver, this invokes jshint + *******************************************************************************/ +/*jslint browser:true*/ +/*global JSHINT define window */ +define(["jshint"], function() { + + function jshint(contents) { + var options={}; + // These were the default for jslint: + // {white: false, onevar: false, undef: true, nomen: false, eqeqeq: true, plusplus: false, + // bitwise: false, regexp: true, newcap: true, immed: true, strict: false, indent: 1}; + if (window.scripted && window.scripted.config && window.scripted.config.jshint) { + if (window.scripted.config.jshint.global) { + options.predef = window.scripted.config.jshint.global; + } + if (window.scripted.config.jshint.options) { + var configOptions = window.scripted.config.jshint.options; + for (var key in configOptions) { + options[key] = configOptions[key]; + } + } + } + JSHINT(contents, options); + return JSHINT.data(); + } + + function cleanup(error) { + function fixWith(fixes, severity, force) { + var description = error.description; + for (var i=0; i < fixes.length; i++) { + var fix = fixes[i], + find = (typeof fix === "string" ? fix : fix[0]), + replace = (typeof fix === "string" ? null : fix[1]), + found = description.indexOf(find) !== -1; + if (force || found) { + error.severity = severity; + } + if (found && replace) { + error.description = replace; + } + } + } + function isBogus() { + var bogus = ["Dangerous comment"], description = error.description; + for (var i=0; i < bogus.length; i++) { + if (description.indexOf(bogus[i]) !== -1) { + return true; + } + } + return false; + } + var warnings = [ + ["Expected '{'", "Statement body should be inside '{ }' braces."] + ]; + var errors = [ + "Missing semicolon", + "Extra comma", + "Missing property name", + "Unmatched ", + " and instead saw", + " is not defined", + "Unclosed string", + "Stopping, unable to continue" + ]; + // All problems are warnings by default + fixWith(warnings, "warning", true); + fixWith(errors, "error"); + return isBogus(error) ? null : error; + } + + /** + * Entry point to this module, runs the linter over the 'contents' and returns a list + * of problems. + */ + function checkSyntax(title, contents) { + var result = jshint(contents); + var problems = []; + var i; + if (result.errors) { + var errors = result.errors; + for (i=0; i < errors.length; i++) { + var error = errors[i]; + if (error) { + var start = error.character - 1, + end = start + 1; + if (error.evidence) { + var index = error.evidence.substring(start).search(/.\b/); + if (index > -1) { + end += index; + } + } + // Convert to format expected by validation service + error.description = error.reason + "(jshint)"; + error.start = error.character; + error.end = end; + error = cleanup(error); + if (error) { problems.push(error); } + } + } + } + if (result.functions) { + var functions = result.functions; + var lines; + for (i=0; i < functions.length; i++) { + var func = functions[i]; + var unused = func.unused; + if (!unused || unused.length === 0) { + continue; + } + if (!lines) { + lines = contents.split(/\r?\n/); + } + var nameGuessed = func.name[0] === '"'; + var name = nameGuessed ? func.name.substring(1, func.name.length - 1) : func.name; + var line = lines[func.line - 1]; + for (var j=0; j < unused.length; j++) { + // Find "function" token in line based on where fName appears. + // nameGuessed implies "foo:function()" or "foo = function()", and !nameGuessed implies "function foo()" + var nameIndex = line.indexOf(name); + var funcIndex = nameGuessed ? line.indexOf("function", nameIndex) : line.lastIndexOf("function", nameIndex); + if (funcIndex !== -1) { + problems.push({ + description: "Function declares unused variable '" + unused[j] + "'.", + line: func.line, + character: funcIndex + 1, + start: funcIndex+1, + end: funcIndex + "function".length, + severity: "warning" + }); + } + } + } + } + return { problems: problems }; + } + + return { + checkSyntax: checkSyntax + }; +}); + diff --git a/client/scripts/scripted/editor/scriptedEditor.js b/client/scripts/scripted/editor/scriptedEditor.js index fbc56d91..56d9267c 100644 --- a/client/scripts/scripted/editor/scriptedEditor.js +++ b/client/scripts/scripted/editor/scriptedEditor.js @@ -18,13 +18,13 @@ define(["require", "orion/textview/textView", "orion/textview/keyBinding", "orion/editor/editor", "orion/editor/editorFeatures", "examples/textview/textStyler", "orion/editor/textMateStyler", "plugins/esprima/esprimaJsContentAssist", "orion/editor/jsTemplateContentAssist", "orion/editor/contentAssist", -"plugins/esprima/indexerService", "orion/editor/jslintdriver", +"plugins/esprima/indexerService", "orion/editor/jslintdriver", "scripted/editor/jshintdriver", "orion/searchAndReplace/textSearcher", "orion/selection", "orion/commands", "orion/parameterCollectors", "orion/editor/htmlGrammar", "plugins/esprima/moduleVerifier", "orion/editor/jslintworker", "jsbeautify","orion/textview/textModel","orion/textview/projectionTextModel", -"orion/editor/htmlContentAssist", "orion/editor/cssContentAssist", "scripted/exec/exec-keys", "scripted/exec/exec-after-save"], +"orion/editor/htmlContentAssist", "orion/editor/cssContentAssist", "scripted/exec/exec-keys", "scripted/exec/exec-after-save","jshint"], function(require, mTextView, mKeyBinding, mEditor, mEditorFeatures, mTextStyler, mTextMateStyler, -mJsContentAssist, mTemplateContentAssist, mContentAssist, mIndexerService, mJslintDriver, mTextSearcher, mSelection, mCommands, mParameterCollectors, +mJsContentAssist, mTemplateContentAssist, mContentAssist, mIndexerService, mJslintDriver, mJshintDriver, mTextSearcher, mSelection, mCommands, mParameterCollectors, mHtmlGrammar, mModuleVerifier, mJsLintWorker, mJsBeautify,mTextModel,mProjectionModel, mHtmlContentAssist, mCssContentAssist) { var determineIndentLevel = function(editor, startPos, options){ @@ -129,7 +129,11 @@ mHtmlContentAssist, mCssContentAssist) { var postSave = function(text) { var problems; if (isJS || isHTML) { - problems = mJslintDriver.checkSyntax('', text).problems; + if (window.scripted.config && window.scripted.config.editor && window.scripted.config.editor.linter && window.scripted.config.editor.linter==='jshint') { + problems = mJshintDriver.checkSyntax('', text).problems; + } else { + problems = mJslintDriver.checkSyntax('', text).problems; + } editor.showProblems(problems); } else { problems = []; From cafad0c27100e57d54886efa369eb186366c6564 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 16:44:19 -0700 Subject: [PATCH 028/931] Fix for SCRIPTED-228 --- .../js-tests/navHIstory/navHistoryTests.js | 126 +++++++++++++++++- client/scripts/scripted/utils/navHistory.js | 21 ++- 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/client/scripts/js-tests/navHIstory/navHistoryTests.js b/client/scripts/js-tests/navHIstory/navHistoryTests.js index 5100f836..63a5284d 100644 --- a/client/scripts/js-tests/navHIstory/navHistoryTests.js +++ b/client/scripts/js-tests/navHIstory/navHistoryTests.js @@ -329,7 +329,131 @@ define(['orion/assert', 'scripted/utils/navHistory', 'setup', 'jquery'], functio // back to original file and ensure selection is grabbed from history mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js" }); assert.deepEqual(window.editor.getSelection(), {start:40,end:50}); - } + }; + + // confirmation after editing + // no edit main --- no confirm + tests.testConfirmNoEditMain = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + } + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js#20,30" }); + + mNavHistory._setNavigationConfirmer(confirmer); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + assert.equal(confirmed, "no", "Should not have opened confirm dialog if no edits"); + }; + // no edit sub --- no confirm + tests.testConfirmNoEditSub = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js#20,30", shiftKey:true }); + mNavHistory._setNavigationConfirmer(confirmer); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + assert.equal(confirmed, "no", "Should not have opened confirm dialog if no edits"); + }; + // edit sub, navigate in main --- no confirm + tests.testConfirmEditSubNavMain = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js#20,30" }); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + window.subeditors[0].setText('foo', 0,0); + + mNavHistory._setNavigationConfirmer(confirmer); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + assert.equal(confirmed, "no", "Should not have opened confirm dialog if no edits"); + }; + // edit main navigate in sub --- no confirm + tests.testConfirmEditMainNavSub = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js#20,30", shiftKey:true }); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + window.editor.setText('foo', 0,0); + + mNavHistory._setNavigationConfirmer(confirmer); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + assert.equal(confirmed, "no", "Should not have opened confirm dialog if no edits"); + }; + + // edit main navigate in main to same file --- no confirm + tests.testConfirmEditMainNavMainSameFile = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + window.editor.setText('foo', 0,0); + + mNavHistory._setNavigationConfirmer(confirmer); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + // should be false and not "no" since the confirmation never occurs if in same file + assert.equal(confirmed, false, "Should not have opened confirm dialog because target is same file"); + }; + // edit sub navigate in sub to same file --- no confirm + tests.testConfirmEditMainNavMainSameFile = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + window.subeditors[0].setText('foo', 0,0); + + mNavHistory._setNavigationConfirmer(confirmer); + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + // should be false and not "no" since the confirmation never occurs if in same file + assert.equal(confirmed, false, "Should not have opened confirm dialog because target is same file"); + }; + + + // edit main navigate in main --- confirm + tests.testConfirmEditMainNavMain = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + mNavHistory._setNavigationConfirmer(confirmer); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30" }); + window.editor.setText('foo', 0,0); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js#20,30" }); + assert.equal(confirmed, "yes", "Should have opened confirm dialog because there was an edit"); + }; + // edit sub navigate in sub --- confirm + tests.testConfirmEditSubNavSub = function() { + var confirmed = false; + function confirmer(done) { + confirmed = done ? "yes" : "no"; + return true; + } + mNavHistory._setNavigationConfirmer(confirmer); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "bar.js#20,30", shiftKey:true }); + window.subeditors[0].setText('foo', 0,0); + + mNavHistory.navigationEventHandler({testTarget : testResourcesRoot + "foo.js#20,30", shiftKey:true }); + assert.equal(confirmed, "yes", "Should have opened confirm dialog because there was an edit"); + }; // still to test diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index d75ad0cb..d76be5e4 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -249,14 +249,30 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD }, 200); }; + var confirmer; + var _setNavigationConfirmer = function(callback) { + confirmer = callback; + }; + var confirmNavigation = function(editor) { + if (editor && editor.isDirty()) { - return confirm("Editor has unsaved changes. Are you sure you want to leave this page? Your changes will be lost."); + if (confirmer) { + // non-blocking mode for tests + confirmer(true); + return true; + } else { + return confirm("Editor has unsaved changes. Are you sure you want to leave this page? Your changes will be lost."); + } } else { + if (confirmer) { + confirmer(false); + } return true; } }; + /** * Opens the given editor on the given definition * @param {{String|Object}} modifier either EDITOR_TARGET.main, EDITOR_TARGET.sub, or EDITOR_TARGET.tab @@ -706,7 +722,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD var targetEditor = target === EDITOR_TARGET.main ? window.editor : window.subeditors[0]; var hasEditor = targetEditor && targetEditor.getText; var isSame = hasEditor && targetEditor.getFilePath() === filepath; - if (!isSame && hasEditor && !confirmNavigation(window.editor)) { + if (!isSame && hasEditor && !confirmNavigation(targetEditor)) { return false; } @@ -769,6 +785,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD return { // private functions that are only exported to help with testing _loadEditor: loadEditor, + _setNavigationConfirmer : _setNavigationConfirmer, // highlightSelection: highlightSelection, don't think we need this openOnRange: openOnRange, From 07103e261072a81f76ea3ae4b2f48868bd899a10 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 26 Oct 2012 20:28:23 -0700 Subject: [PATCH 029/931] SCRIPTED-230, Consistent Ctrl/Shift Click navigation story. --- client/scripts/scripted/utils/navHistory.js | 55 ++++++++++++------- .../scripted/widgets/OpenResourceDialog.js | 7 +-- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/client/scripts/scripted/utils/navHistory.js b/client/scripts/scripted/utils/navHistory.js index d76be5e4..6e3b70c9 100644 --- a/client/scripts/scripted/utils/navHistory.js +++ b/client/scripts/scripted/utils/navHistory.js @@ -170,7 +170,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD -Breadcrumb -Open File */ - var navigationEventHandler = function(event) { + var navigationEventHandler = function(event, editor) { var filepath = event.testTarget ? event.testTarget : ( event.altTarget ? $(event.altTarget).attr('href') : $(event.currentTarget).attr('href')); var query_index = filepath.indexOf('?'); @@ -201,7 +201,19 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD return false; } + + var target = findTarget(event); + if (editor) { + // if coming from sub-editor, we want to stay in same editor if no modifiers are used + if (editor.type === EDITOR_TARGET.sub) { + if (target === EDITOR_TARGET.sub) { + target = EDITOR_TARGET.main; + } else if (target === EDITOR_TARGET.main) { + target = EDITOR_TARGET.sub; + } + } + } navigate(filepath, range, target, true); return false; }; @@ -295,6 +307,16 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD target = modifier; } if (target) { + if (editor) { + // if coming from sub-editor, we want to stay in same editor if no modifiers are used + if (editor.type === EDITOR_TARGET.sub) { + if (target === EDITOR_TARGET.sub) { + target = EDITOR_TARGET.main; + } else if (target === EDITOR_TARGET.main) { + target = EDITOR_TARGET.sub; + } + } + } navigate(filepath, defnrange, target, true); } }; @@ -305,7 +327,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD var offset = editor.getTextView().getOffsetAtLocation(rect.x, rect.y); var definition = editor.findDefinition(offset); if (definition) { - openOnRange(event.shiftKey ? "sub" : "main", definition, editor); + openOnRange(event.shiftKey ? EDITOR_TARGET.sub : EDITOR_TARGET.main, definition, editor); } } } @@ -541,7 +563,8 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD // from globalCommands.js var openOutlineDialog = function(searcher, serviceRegistry, editor) { var dialog = new scripted.widgets.OpenOutlineDialog({ - changeFile: navigationEventHandler, + // TODO FIXADE Do we need this? +// changeFile: navigationEventHandler, editor: editor }); if (editor) { @@ -598,7 +621,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD // TODO move to scriptedEditor.js var attachDefinitionNavigation = function(editor) { editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ false, /*shift*/ false, /*alt*/ false), "Open declaration"); - editor.getTextView().setAction("Open declaration", function() { + editor.getTextView().setAction("Open declaration in same editor", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { openOnRange(EDITOR_TARGET.main, definition, editor); @@ -612,7 +635,7 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD } }); editor.getTextView().setKeyBinding(new mKeyBinding.KeyBinding(/*F8*/ 119, /*command/ctrl*/ false, /*shift*/ true, /*alt*/ false), "Open declaration in subeditor"); - editor.getTextView().setAction("Open declaration in subeditor", function() { + editor.getTextView().setAction("Open declaration in other editor", function() { var definition = editor.findDefinition(editor.getTextView().getCaretOffset()); if (definition) { openOnRange(EDITOR_TARGET.sub, definition, editor); @@ -669,22 +692,14 @@ function(mEditor, mKeyBinding, mSearchClient, mOpenResourceDialog, mOpenOutlineD * handles the onpopstate event */ var popstateHandler = function(event) { - var cont = true; - if (window.editor.isDirty() || (window.subeditors[0] && window.subeditors[0].isDirty())) { - cont = confirm("Editor has unsaved changes. Are you sure you want to leave this page? Your changes will be lost."); - } - if (cont) { - var target = findTarget(event); - var state = event.originalEvent.state; - if (state && state.filepath) { - navigate(state.filepath, state.range, target); - return false; - } else { - return true; - } - } else { + var target = findTarget(event); + var state = event.originalEvent.state; + if (state && state.filepath) { + navigate(state.filepath, state.range, target); return false; - } + } else { + return true; + } }; diff --git a/client/scripts/scripted/widgets/OpenResourceDialog.js b/client/scripts/scripted/widgets/OpenResourceDialog.js index 062a4f52..a101fcb3 100644 --- a/client/scripts/scripted/widgets/OpenResourceDialog.js +++ b/client/scripts/scripted/widgets/OpenResourceDialog.js @@ -11,7 +11,7 @@ * Andy Clement - tailored for use in scripted *******************************************************************************/ /*jslint browser:true*/ -/*global define orion window dojo dijit*/ +/*global define orion window dojo dijit scripted*/ define(['require', 'dojo', 'dijit', 'dijit/Dialog', 'dijit/form/TextBox', 'scripted/widgets/_OrionDialogMixin', 'text!scripted/widgets/templates/OpenResourceDialog.html'], @@ -239,10 +239,7 @@ var OpenResourceDialog = dojo.declare("scripted.widgets.OpenResourceDialog", [di dojo.query("a", resultsDiv).forEach(function(resourceLink) { dojo.connect(resourceLink, 'onclick', function(evt){ widget.hide(); - if (editor && editor.type === 'sub'){ - evt.makeShift = true; - } - var ret = changeFile(evt); + var ret = changeFile(evt, editor); if (!ret) { dojo.stopEvent(evt); } }); }); From ed384b298b722803ec086cb7cb6a658d6055099c Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Sun, 28 Oct 2012 10:12:12 -0700 Subject: [PATCH 030/931] A slighly better looking console-toggle button immage. --- client/css/main.css | 2 +- client/images/console.png | Bin 0 -> 426 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 client/images/console.png diff --git a/client/css/main.css b/client/css/main.css index ddfaa92f..70428ac9 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -194,7 +194,7 @@ footer{ #console_toggle { margin: 4px 5px 0 0; cursor:pointer; - background-image: url(../images/gnome-terminal_16.png); + background-image: url(../images/console.png); width:16px; height:16px; } diff --git a/client/images/console.png b/client/images/console.png new file mode 100644 index 0000000000000000000000000000000000000000..a2054d4221e10173298d4edf3bc569d0646ba469 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^OE% zV4B_c9H0nifk$L90|U1(2s1Lwnj--eWH0gbb!ET9B`u&NaA>jjf1r?LiEBiObAE1a zYF-J0b5UwyNotBhd1gt5g1e`0KzJjcIM6CqPZ!4!i_=FZ8|EE0;Bno5=>Jk(*a|oz#J@_5Jgyl}PO1Zn&$>}P)e;gtQ$o`xK>mSb7yW0~I(|&AaT7 zRbp#peJ}fbQ%(%wO?b0D{K4Ahx8Ec!S8H+C-v6I@yY9bTV|v3z zHYsk6r0vzKbwjooy6djcR$%;5@csHye$Ab~wU&O_{6Ta7oxjG9#G|(v2>G@yY6ONF NgQu&X%Q~loCIE!Lo)!QA literal 0 HcmV?d00001 From 1f20afe691a90efdb34518d6897154590f710a57 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 29 Oct 2012 11:11:17 -0700 Subject: [PATCH 031/931] Implementation of commonjs resolver based on an external resolver library plus some additional regression tests. --- server/jsdepend/commonjs-resolver-OLD.js | 10 ++ server/jsdepend/commonjs-resolver.js | 3 +- server/jsdepend/debug.js | 3 + server/jsdepend/node-modules-resolver.js | 148 ++++++++++++------ server/jsdepend/resolver-test.js | 60 +++++++ .../project/libs/bar/index.js | 0 .../project/libs/foo.js | 0 .../project/libs/zor/lib/zor-main.js | 0 .../project/libs/zor/package.json | 3 + .../project/main.js | 0 .../project/snif/index.js | 0 .../project/snif/package.json | 3 + .../project/snif/sniffer.js | 0 server/jsdepend/utils.js | 32 ++++ 14 files changed, 209 insertions(+), 53 deletions(-) create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/bar/index.js create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/foo.js create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/lib/zor-main.js create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/package.json create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/main.js create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/index.js create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/package.json create mode 100644 server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/sniffer.js diff --git a/server/jsdepend/commonjs-resolver-OLD.js b/server/jsdepend/commonjs-resolver-OLD.js index ddc478b0..55ce13d0 100644 --- a/server/jsdepend/commonjs-resolver-OLD.js +++ b/server/jsdepend/commonjs-resolver-OLD.js @@ -11,6 +11,16 @@ * Kris De Volder - initial API and implementation ******************************************************************************/ +// +// This is the old 'home backed' implementation. At the moment it is not +// used anymore. It has been replaced by something that calls the +// 'enhanced-resolve' node module. +// +// We are keeping it around still until we can make a final decision about +// going with 'enhanced-resolve' is indeed the way to go forward. +// See some discussion in here: +// https://issuetracker.springsource.com/browse/SCRIPTED-117 + /*global resolve require define esprima console module process*/ if (typeof define !== 'function') { var define = require('amdefine')(module); diff --git a/server/jsdepend/commonjs-resolver.js b/server/jsdepend/commonjs-resolver.js index a8efeb43..2eec756c 100644 --- a/server/jsdepend/commonjs-resolver.js +++ b/server/jsdepend/commonjs-resolver.js @@ -55,7 +55,8 @@ function configure(conf) { // //For now don't need any config, we only support resolving of './' references. // //and that only require access to the location of the current file. // } - + + function resolver(context, dep, callback) { if (nodeNatives.isNativeNodeModule(dep.name)) { dep.path = nodeNatives.MAGIC_PATH_PREFIX + dep.name; diff --git a/server/jsdepend/debug.js b/server/jsdepend/debug.js index 9c84d653..0437bf14 100644 --- a/server/jsdepend/debug.js +++ b/server/jsdepend/debug.js @@ -17,6 +17,9 @@ //THIS CODE IS NOT PART OF scripted (its not loaded by any other module) + +require.resolve('/home/kdvolder/commandline-dev/new-tools/scripted/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif'); + //var testCase = require('./module-types-test.js').bigFile; var testCase = require('./resolver-test.js').resolveNodeModulesWithProblem3; diff --git a/server/jsdepend/node-modules-resolver.js b/server/jsdepend/node-modules-resolver.js index 9bbee71f..110650aa 100644 --- a/server/jsdepend/node-modules-resolver.js +++ b/server/jsdepend/node-modules-resolver.js @@ -78,21 +78,104 @@ function configure(conf) { // with an or. // A 'computation' is a function that accepts just one parameter which is a callback and it passes // a value to that callback. - function ork(comp1, comp2) { - //isCovered(); - return function(k) { - comp1(function (v1) { - if (v1){ - //isCovered(); - k(v1); - } else { - //isCovered(); - comp2(k); +// function ork(comp1, comp2) { +// //isCovered(); +// return function(k) { +// comp1(function (v1) { +// if (v1){ +// //isCovered(); +// k(v1); +// } else { +// //isCovered(); +// comp2(k); +// } +// }); +// }; +// } + + var ork = require('./utils').ork; + + function maybeAddJs(str) { + if (endsWith(str, '.js')) { + //isCovered(); + return str; + } else { + //isCovered(); + return str + '.js'; + } + } + + function getPathFromJsonFile(jsonFile, k) { + getContents(jsonFile, + function (text) { + //isCovered(); + try { + var json = JSON.parse(text); + var path = json && json.main; + if (typeof(path)==='string') { + //isCovered(); + path = maybeAddJs(pathResolve(getDirectory(jsonFile), path)); + return k(path); + } else { + //isCovered(); by putting '"main" : 88 in package.json + return k(); + } + } catch (e) { + //isCovered(); by unparseable package.json + //Assume its not proper JSON data: ignore and continue } - }); - }; + k(); + }, + function (err) { + //isCovered(); // by module dir that has neither index.js nor package.json + //console.log(err); + k(); + } + ); } - + + function absoluteDirResolve(path, dep, k) { + ork( + //Try .js extension + function (k) { + var file = path+".js"; + isFile(file, function (is) { + k(is && file); + }); + }, + //Try index.js + function (k) { + var indexjs = pathResolve(path, 'index.js'); + isFile(indexjs, function (is) { + //isCovered(); + k(is && indexjs); + }); + }, + //Try package.json + function(k) { + //isCovered(); + var jsonFile = pathResolve(path, 'package.json'); + getPathFromJsonFile(jsonFile, function(path) { + isFile(path, function(is) { + //isCovered(); + if (is) { + //isCovered(); + k(path); + } else { + //isCovered(); by mispelled file name in package.json + k(false); + } + }); + }); + } + )(function (resolvedPath) { + if (resolvedPath) { + dep.path = resolvedPath; + } + k(dep); + }); + } + function createResolver(dir) { var modulesMap = {}; @@ -117,45 +200,6 @@ function configure(conf) { var ready = new Ready(); //Becomes 'true' upon completion of building the table. var dirty = true; // Becomes true when file system changes invalidate the contents of the table. // meaning: the map needs a (re)build to be initiated. - - function maybeAddJs(str) { - if (endsWith(str, '.js')) { - //isCovered(); - return str; - } else { - //isCovered(); - return str + '.js'; - } - } - - function getPathFromJsonFile(jsonFile, k) { - getContents(jsonFile, - function (text) { - //isCovered(); - try { - var json = JSON.parse(text); - var path = json && json.main; - if (typeof(path)==='string') { - //isCovered(); - path = maybeAddJs(pathResolve(getDirectory(jsonFile), path)); - return k(path); - } else { - //isCovered(); by putting '"main" : 88 in package.json - return k(); - } - } catch (e) { - //isCovered(); by unparseable package.json - //Assume its not proper JSON data: ignore and continue - } - k(); - }, - function (err) { - //isCovered(); // by module dir that has neither index.js nor package.json - //console.log(err); - k(); - } - ); - } function addModule(name, path) { if (!modulesMap.hasOwnProperty(name)) { diff --git a/server/jsdepend/resolver-test.js b/server/jsdepend/resolver-test.js index 150d8c26..c7ce6744 100644 --- a/server/jsdepend/resolver-test.js +++ b/server/jsdepend/resolver-test.js @@ -476,4 +476,64 @@ exports.resolveDotDotReference = function (test) { test.done(); }); +}; + +exports.resolveDifferentStyleRelativeDotRefs = function (test) { + var api = makeApi('node-with-different-relative-refs'); + var depNames = ['./libs/foo', './libs/bar', './libs/zor', './snif']; + var expectedPaths = [ + 'project/libs/foo.js', + 'project/libs/bar/index.js', + 'project/libs/zor/lib/zor-main.js', + 'project/snif/sniffer.js' + ]; + + var deps = map(depNames, function (name) { + return { + name: name, + kind: 'commonjs' + }; + }); + mapk(deps, function (dep, k) { + api.resolve('project/main.js', dep, k); + }, + function (resolveds) { + test.equals(resolveds.length, expectedPaths.length); + for (var i = 0; i < expectedPaths.length; i++) { + test.equals(expectedPaths[i], resolveds[i].path); + } + //console.log(resolveds); + test.done(); + } + ); +}; + +exports.resolveDifferentStyleRelativeDotDotRefs = function (test) { + var api = makeApi('node-with-different-relative-refs'); + var depNames = ['../project/libs/foo', '../project/libs/bar', '../project/libs/zor', '../project/snif']; + var expectedPaths = [ + 'project/libs/foo.js', + 'project/libs/bar/index.js', + 'project/libs/zor/lib/zor-main.js', + 'project/snif/sniffer.js' + ]; + + var deps = map(depNames, function (name) { + return { + name: name, + kind: 'commonjs' + }; + }); + mapk(deps, function (dep, k) { + api.resolve('project/main.js', dep, k); + }, + function (resolveds) { + test.equals(resolveds.length, expectedPaths.length); + for (var i = 0; i < expectedPaths.length; i++) { + test.equals(expectedPaths[i], resolveds[i].path); + } + //console.log(resolveds); + test.done(); + } + ); }; \ No newline at end of file diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/bar/index.js b/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/bar/index.js new file mode 100644 index 00000000..e69de29b diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/foo.js b/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/foo.js new file mode 100644 index 00000000..e69de29b diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/lib/zor-main.js b/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/lib/zor-main.js new file mode 100644 index 00000000..e69de29b diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/package.json b/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/package.json new file mode 100644 index 00000000..8e80e06c --- /dev/null +++ b/server/jsdepend/test-resources/node-with-different-relative-refs/project/libs/zor/package.json @@ -0,0 +1,3 @@ +{ + "main" : "lib/zor-main.js" +} diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/main.js b/server/jsdepend/test-resources/node-with-different-relative-refs/project/main.js new file mode 100644 index 00000000..e69de29b diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/index.js b/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/index.js new file mode 100644 index 00000000..e69de29b diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/package.json b/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/package.json new file mode 100644 index 00000000..7a45a45a --- /dev/null +++ b/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/package.json @@ -0,0 +1,3 @@ +{ + "main" : "sniffer" +} diff --git a/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/sniffer.js b/server/jsdepend/test-resources/node-with-different-relative-refs/project/snif/sniffer.js new file mode 100644 index 00000000..e69de29b diff --git a/server/jsdepend/utils.js b/server/jsdepend/utils.js index 63c40ee4..bf8bb763 100644 --- a/server/jsdepend/utils.js +++ b/server/jsdepend/utils.js @@ -252,6 +252,37 @@ function extend(proto, addProps) { } return obj; } + +/** + * Continuation passing style 'or' function. It takes variable number of computations + * as an argument. Each computation is a one-arg function that expects a callback. + *

    + * The ork function returns a computation that tries each argument from left to + * to right until one computations yields a truthy value to its callback. This + * value is passed to the or callback. If none of the arguments yields a true + * value then or callback is passed a falsy value. + */ +function ork() { + var theArgs = arguments; + return function(k) { + function loop(i) { + if (i Date: Mon, 29 Oct 2012 11:21:37 -0700 Subject: [PATCH 032/931] Add warning when webworker not available. --- client/scripts/plugins/esprima/indexerService.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/scripts/plugins/esprima/indexerService.js b/client/scripts/plugins/esprima/indexerService.js index ae0fd49c..7ffb77b3 100644 --- a/client/scripts/plugins/esprima/indexerService.js +++ b/client/scripts/plugins/esprima/indexerService.js @@ -57,6 +57,10 @@ define(["plugins/esprima/esprimaJsContentAssist", "servlets/jsdepend-client"], f throw e; } } + } else { + if (this.comsole) { + this.console.warn("Web worker not available for background indexing. Falling back to in-browser indexing."); + } } From c864520c33d5404ed7f3bf78df9413ca8da46c7a Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 29 Oct 2012 16:04:38 -0700 Subject: [PATCH 033/931] SCRIPTED-229: ask server for more results when scrolling near bottom of text search results --- client/scripts/scripted/fileSearchClient.js | 1 - .../scripts/scripted/widgets/SearchDialog.js | 25 +++++++++++++++++++ .../incremental-file-search-client.js | 7 ++++++ .../incremental-file-search-servlet.js | 24 ++++++++++++++---- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/client/scripts/scripted/fileSearchClient.js b/client/scripts/scripted/fileSearchClient.js index fa29f1c7..9ff4cfe7 100644 --- a/client/scripts/scripted/fileSearchClient.js +++ b/client/scripts/scripted/fileSearchClient.js @@ -90,7 +90,6 @@ define(['require', 'dojo', 'dijit', /*'orion/auth',*/ 'orion/util', 'orion/searc } }); return activeSearch; - }, handleError: function(response, resultsNode) { diff --git a/client/scripts/scripted/widgets/SearchDialog.js b/client/scripts/scripted/widgets/SearchDialog.js index 152d841c..dd6c2c4b 100644 --- a/client/scripts/scripted/widgets/SearchDialog.js +++ b/client/scripts/scripted/widgets/SearchDialog.js @@ -268,6 +268,28 @@ var SearchDialog = dojo.declare("scripted.widgets.SearchDialog", [dijit.Dialog, //console.log(msg); }, + addMoreResultsNearScrollBottom: function () { + var target = this.results; + var activeFileSearch = this.activeFileSearch; + if (activeFileSearch && target) { +// console.log('scrollTop: '+target.scrollTop); +// console.log('scrollHeight: '+target.scrollHeight); +// console.log('clientHeight: '+target.clientHeight); +// console.log('clientHeight + scrollTop: '+(target.clientHeight + target.scrollTop)); + + var scrollBottom = target.scrollTop + target.clientHeight; + var scrollHeight = target.scrollHeight; + if (scrollHeight) { + var leftOver = (scrollHeight - scrollBottom) / scrollHeight; + //console.log('left over %: '+Math.floor(leftOver*100)); + if (leftOver < 0.1) { + //Less than 10% of the elements displayed below bottom of the visible scroll area. + this.activeFileSearch.more(); //ask more results. + } + } + } + }, + /** @private */ render: function() { var text = this.resourceName && this.resourceName.get("value"); @@ -286,6 +308,9 @@ var SearchDialog = dojo.declare("scripted.widgets.SearchDialog", [dijit.Dialog, var renderer = that.fileSearchRenderer.makeIncrementalRenderer(that.results,false, null,dojo.hitch(that,that.decorateResult)); that.activeFileSearch = that.fileSearcher.search(text,false,renderer); + that.results.addEventListener('scroll', function (evt) { + that.addMoreResultsNearScrollBottom(); + }); } else { that.debug("SearchDialog: search active, updating it"); activeFileSearch.query(text); diff --git a/client/scripts/servlets/incremental-file-search-client.js b/client/scripts/servlets/incremental-file-search-client.js index 5c352d69..82a7ed33 100644 --- a/client/scripts/servlets/incremental-file-search-client.js +++ b/client/scripts/servlets/incremental-file-search-client.js @@ -94,6 +94,13 @@ define(["sockjs"], function () { */ query: function (newQuery) { send({requery: [newQuery]}); + }, + /** + * Ask for more results. If the server-side process is currently paused + * Then its result limit will be raised and the process resumed. + */ + more: function () { + send({more: []}); } }; } diff --git a/server/servlets/incremental-file-search-servlet.js b/server/servlets/incremental-file-search-servlet.js index bbd48acf..f94a1138 100644 --- a/server/servlets/incremental-file-search-servlet.js +++ b/server/servlets/incremental-file-search-servlet.js @@ -30,11 +30,11 @@ var searchFile = require('../textsearch/searcher').searchFile; var fswalk = require('../jsdepend/fswalk').configure(conf).asynchWalk; var LOG_SOCKET_COUNT = true; -var MAX_RESULTS = 30; // When this number is reached, then the walker will be paused. +var MAX_RESULTS_DEFAULT = 30; // When this number is reached, then the walker will be paused. // Note that the walker can not be paused in the middle of a file // just yet (the contents of the file is not walked in a pausable way) // So the number of results may still exceed this limit. - + exports.install = function (server) { var openSockets = 0; @@ -43,6 +43,9 @@ exports.install = function (server) { sockServer.on('connection', function (conn) { //opening a websocket connection initiates a search. + var maxResults = null; // set upon receipt of the initial query + // can also be increased by a request for more results. + if (LOG_SOCKET_COUNT) { openSockets++; console.log('ifsearch socket opened ['+openSockets+']'); @@ -104,7 +107,7 @@ exports.install = function (server) { //console.log("Sending result "+JSON.stringify(result)); send({add: [result]}); resultCount++; - if (resultCount >= MAX_RESULTS && activeWalker) { + if (resultCount >= maxResults && activeWalker) { activeWalker.pause(); } } @@ -242,7 +245,8 @@ exports.install = function (server) { // } query = q; options = opts || {}; - //console.log('search options: '+JSON.stringify(options)); + maxResults = options.maxResults || MAX_RESULTS_DEFAULT; + console.log('maxResults = '+maxResults); activeWalker = startSearch(); // fileindexer.getIndexer(currentFile, function (ixr) { // indexer = ixr; @@ -252,6 +256,16 @@ exports.install = function (server) { console.error("multiple queries received on the same /ifsearch connection"); } }, + //ask for more results: increases maxResults to be at least the current number + //of results plus 10% + more: function () { + if (activeWalker) { + maxResults = Math.max(maxResults, resultCount * 1.1); + console.log('maxResults = '+maxResults); + activeWalker.resume(); + } + }, + //change the current query, possibly in midrun requery: function (q) { //console.log('change query '+query+' => ' + q); @@ -278,7 +292,7 @@ exports.install = function (server) { } } } - if (resultCount Date: Mon, 29 Oct 2012 16:08:37 -0700 Subject: [PATCH 034/931] Fix a typo 'comsole' = 'console' --- client/scripts/plugins/esprima/indexerService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/scripts/plugins/esprima/indexerService.js b/client/scripts/plugins/esprima/indexerService.js index 7ffb77b3..a4bdfb32 100644 --- a/client/scripts/plugins/esprima/indexerService.js +++ b/client/scripts/plugins/esprima/indexerService.js @@ -58,7 +58,7 @@ define(["plugins/esprima/esprimaJsContentAssist", "servlets/jsdepend-client"], f } } } else { - if (this.comsole) { + if (this.console) { this.console.warn("Web worker not available for background indexing. Falling back to in-browser indexing."); } } From 03a4f2757c31f64763a9c41e66c8323941866039 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 29 Oct 2012 16:17:43 -0700 Subject: [PATCH 035/931] Raise minimu text search length to 3 letters (problems otherwise searching in huge files such as the profiler results data in HtmlParser --- client/scripts/scripted/widgets/SearchDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/scripts/scripted/widgets/SearchDialog.js b/client/scripts/scripted/widgets/SearchDialog.js index dd6c2c4b..b79a5e9b 100644 --- a/client/scripts/scripted/widgets/SearchDialog.js +++ b/client/scripts/scripted/widgets/SearchDialog.js @@ -298,7 +298,7 @@ var SearchDialog = dojo.declare("scripted.widgets.SearchDialog", [dijit.Dialog, //even with a 'suspendable' search, because the search cannot be suspended in the //midle of a file (yet). With a big file to search the single char search can //return a lot of results just in that one file... causing trouble. - if (text && text.length>=2) { + if (text && text.length>=3) { this.debug("SearchDialog: text changed '"+text+"'"); var that = this; setTimeout(function() { From 1a9db0dbe02e1589f97e6561326392f787634a54 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 29 Oct 2012 16:21:17 -0700 Subject: [PATCH 036/931] Raise minimu text search length to 3 letters (problems otherwise searching in huge files such as the profiler results data in HtmlParser --- client/scripts/scripted/widgets/SearchDialog.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/scripts/scripted/widgets/SearchDialog.js b/client/scripts/scripted/widgets/SearchDialog.js index b79a5e9b..2a3c7013 100644 --- a/client/scripts/scripted/widgets/SearchDialog.js +++ b/client/scripts/scripted/widgets/SearchDialog.js @@ -17,6 +17,8 @@ define(['require', 'dojo', 'dijit', 'dijit/Dialog', 'dijit/form/TextBox', 'scripted/widgets/_OrionDialogMixin', 'text!scripted/widgets/templates/SearchDialog.html'], function(require, dojo, dijit, mFileLoader) { + +var MINIMUM_LENGTH = 3; //Search Strings smaller than this are considered problematic and not executed. /** * Quick and dirty search history. It is not persisted and only retains a single search result. @@ -30,7 +32,7 @@ var searchHistory = (function () { return lastSearch; }, put: function (search) { - if (typeof(search)==='string' && search.length>2) { + if (typeof(search)==='string' && search.length>=MINIMUM_LENGTH) { //Ignore trivial or empty searches. Probably more useful to keep the //previous search instead. lastSearch = search; @@ -298,7 +300,7 @@ var SearchDialog = dojo.declare("scripted.widgets.SearchDialog", [dijit.Dialog, //even with a 'suspendable' search, because the search cannot be suspended in the //midle of a file (yet). With a big file to search the single char search can //return a lot of results just in that one file... causing trouble. - if (text && text.length>=3) { + if (text && text.length>=MINIMUM_LENGTH) { this.debug("SearchDialog: text changed '"+text+"'"); var that = this; setTimeout(function() { From 4015c5ef92612a2dca49b204002b8fd1a3367375 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 29 Oct 2012 16:46:49 -0700 Subject: [PATCH 037/931] Remove some old stuff from play-area --- play-area/try-dnode/.gitignore | 1 - play-area/try-dnode/browser/dnode-client.js | 17 - play-area/try-dnode/browser/dnode-server.js | 41 - play-area/try-dnode/browser/static/bundle.js | 4698 ----------------- play-area/try-dnode/browser/static/index.html | 2 - play-area/try-dnode/just-node/dnode-client.js | 9 - play-area/try-dnode/just-node/dnode-server.js | 32 - play-area/try-dnode/package.json | 14 - 8 files changed, 4814 deletions(-) delete mode 100644 play-area/try-dnode/.gitignore delete mode 100644 play-area/try-dnode/browser/dnode-client.js delete mode 100644 play-area/try-dnode/browser/dnode-server.js delete mode 100644 play-area/try-dnode/browser/static/bundle.js delete mode 100644 play-area/try-dnode/browser/static/index.html delete mode 100644 play-area/try-dnode/just-node/dnode-client.js delete mode 100644 play-area/try-dnode/just-node/dnode-server.js delete mode 100644 play-area/try-dnode/package.json diff --git a/play-area/try-dnode/.gitignore b/play-area/try-dnode/.gitignore deleted file mode 100644 index 3c3629e6..00000000 --- a/play-area/try-dnode/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/play-area/try-dnode/browser/dnode-client.js b/play-area/try-dnode/browser/dnode-client.js deleted file mode 100644 index becdb8be..00000000 --- a/play-area/try-dnode/browser/dnode-client.js +++ /dev/null @@ -1,17 +0,0 @@ -/*global require document*/ -var domready = require('domready'); -var shoe = require('shoe'); -var dnode = require('dnode'); - -domready(function () { - var result = document.getElementById('result'); - var stream = shoe('/dnode'); - - var d = dnode(); - d.on('remote', function (remote) { - remote.search('beep', function (s) { - result.textContent = result.textContent+"\n"+s; - }); - }); - d.pipe(stream).pipe(d); -}); \ No newline at end of file diff --git a/play-area/try-dnode/browser/dnode-server.js b/play-area/try-dnode/browser/dnode-server.js deleted file mode 100644 index e617bf32..00000000 --- a/play-area/try-dnode/browser/dnode-server.js +++ /dev/null @@ -1,41 +0,0 @@ -/*global require console setTimeout __dirname */ - -//See https://github.com/substack/dnode -// for info on how to 'build' and run this with 'browserify'. - -var http = require('http'); -var shoe = require('shoe'); -var ecstatic = require('ecstatic')(__dirname + '/static'); -var dnode = require('dnode'); - -var server = http.createServer(ecstatic); -server.listen(9999); - -function search(query, callback) { - console.log("Search started..."); - function sendResults(i) { - if (i < 10) { - var result = query+":"+i; - console.log("Sending: "+result); - callback(result); - setTimeout(function() { - sendResults(i + 1); - }, - 1000); - } else { - console.log("Search ended"); - callback("DONE"); - } - } - sendResults(0); -} - -var sock = shoe(function (stream) { - var d = dnode({ - search : search - }); - d.pipe(stream).pipe(d); -}); -sock.install(server, '/dnode'); - -console.log("Browse to http://localhost:9999/"); \ No newline at end of file diff --git a/play-area/try-dnode/browser/static/bundle.js b/play-area/try-dnode/browser/static/bundle.js deleted file mode 100644 index 99915ff8..00000000 --- a/play-area/try-dnode/browser/static/bundle.js +++ /dev/null @@ -1,4698 +0,0 @@ -(function(){var require = function (file, cwd) { - var resolved = require.resolve(file, cwd || '/'); - var mod = require.modules[resolved]; - if (!mod) throw new Error( - 'Failed to resolve module ' + file + ', tried ' + resolved - ); - var cached = require.cache[resolved]; - var res = cached? cached.exports : mod(); - return res; -}; - -require.paths = []; -require.modules = {}; -require.cache = {}; -require.extensions = [".js",".coffee"]; - -require._core = { - 'assert': true, - 'events': true, - 'fs': true, - 'path': true, - 'vm': true -}; - -require.resolve = (function () { - return function (x, cwd) { - if (!cwd) cwd = '/'; - - if (require._core[x]) return x; - var path = require.modules.path(); - cwd = path.resolve('/', cwd); - var y = cwd || '/'; - - if (x.match(/^(?:\.\.?\/|\/)/)) { - var m = loadAsFileSync(path.resolve(y, x)) - || loadAsDirectorySync(path.resolve(y, x)); - if (m) return m; - } - - var n = loadNodeModulesSync(x, y); - if (n) return n; - - throw new Error("Cannot find module '" + x + "'"); - - function loadAsFileSync (x) { - x = path.normalize(x); - if (require.modules[x]) { - return x; - } - - for (var i = 0; i < require.extensions.length; i++) { - var ext = require.extensions[i]; - if (require.modules[x + ext]) return x + ext; - } - } - - function loadAsDirectorySync (x) { - x = x.replace(/\/+$/, ''); - var pkgfile = path.normalize(x + '/package.json'); - if (require.modules[pkgfile]) { - var pkg = require.modules[pkgfile](); - var b = pkg.browserify; - if (typeof b === 'object' && b.main) { - var m = loadAsFileSync(path.resolve(x, b.main)); - if (m) return m; - } - else if (typeof b === 'string') { - var m = loadAsFileSync(path.resolve(x, b)); - if (m) return m; - } - else if (pkg.main) { - var m = loadAsFileSync(path.resolve(x, pkg.main)); - if (m) return m; - } - } - - return loadAsFileSync(x + '/index'); - } - - function loadNodeModulesSync (x, start) { - var dirs = nodeModulesPathsSync(start); - for (var i = 0; i < dirs.length; i++) { - var dir = dirs[i]; - var m = loadAsFileSync(dir + '/' + x); - if (m) return m; - var n = loadAsDirectorySync(dir + '/' + x); - if (n) return n; - } - - var m = loadAsFileSync(x); - if (m) return m; - } - - function nodeModulesPathsSync (start) { - var parts; - if (start === '/') parts = [ '' ]; - else parts = path.normalize(start).split('/'); - - var dirs = []; - for (var i = parts.length - 1; i >= 0; i--) { - if (parts[i] === 'node_modules') continue; - var dir = parts.slice(0, i + 1).join('/') + '/node_modules'; - dirs.push(dir); - } - - return dirs; - } - }; -})(); - -require.alias = function (from, to) { - var path = require.modules.path(); - var res = null; - try { - res = require.resolve(from + '/package.json', '/'); - } - catch (err) { - res = require.resolve(from, '/'); - } - var basedir = path.dirname(res); - - var keys = (Object.keys || function (obj) { - var res = []; - for (var key in obj) res.push(key); - return res; - })(require.modules); - - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (key.slice(0, basedir.length + 1) === basedir + '/') { - var f = key.slice(basedir.length); - require.modules[to + f] = require.modules[basedir + f]; - } - else if (key === basedir) { - require.modules[to] = require.modules[basedir]; - } - } -}; - -(function () { - var process = {}; - - require.define = function (filename, fn) { - if (require.modules.__browserify_process) { - process = require.modules.__browserify_process(); - } - - var dirname = require._core[filename] - ? '' - : require.modules.path().dirname(filename) - ; - - var require_ = function (file) { - var requiredModule = require(file, dirname); - var cached = require.cache[require.resolve(file, dirname)]; - - if (cached && cached.parent === null) { - cached.parent = module_; - } - - return requiredModule; - }; - require_.resolve = function (name) { - return require.resolve(name, dirname); - }; - require_.modules = require.modules; - require_.define = require.define; - require_.cache = require.cache; - var module_ = { - id : filename, - filename: filename, - exports : {}, - loaded : false, - parent: null - }; - - require.modules[filename] = function () { - require.cache[filename] = module_; - fn.call( - module_.exports, - require_, - module_, - module_.exports, - dirname, - filename, - process - ); - module_.loaded = true; - return module_.exports; - }; - }; -})(); - - -require.define("path",function(require,module,exports,__dirname,__filename,process){function filter (xs, fn) { - var res = []; - for (var i = 0; i < xs.length; i++) { - if (fn(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} - -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length; i >= 0; i--) { - var last = parts[i]; - if (last == '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } - - return parts; -} - -// Regex to split a filename into [*, dir, basename, ext] -// posix version -var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; - -// path.resolve([from ...], to) -// posix version -exports.resolve = function() { -var resolvedPath = '', - resolvedAbsolute = false; - -for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) - ? arguments[i] - : process.cwd(); - - // Skip empty and invalid entries - if (typeof path !== 'string' || !path) { - continue; - } - - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; -} - -// At this point the path should be resolved to a full absolute path, but -// handle relative paths to be safe (might happen when process.cwd() fails) - -// Normalize the path -resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); - - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; -}; - -// path.normalize(path) -// posix version -exports.normalize = function(path) { -var isAbsolute = path.charAt(0) === '/', - trailingSlash = path.slice(-1) === '/'; - -// Normalize the path -path = normalizeArray(filter(path.split('/'), function(p) { - return !!p; - }), !isAbsolute).join('/'); - - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - - return (isAbsolute ? '/' : '') + path; -}; - - -// posix version -exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - return p && typeof p === 'string'; - }).join('/')); -}; - - -exports.dirname = function(path) { - var dir = splitPathRe.exec(path)[1] || ''; - var isWindows = false; - if (!dir) { - // No dirname - return '.'; - } else if (dir.length === 1 || - (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { - // It is just a slash or a drive letter with a slash - return dir; - } else { - // It is a full dirname, strip trailing slash - return dir.substring(0, dir.length - 1); - } -}; - - -exports.basename = function(path, ext) { - var f = splitPathRe.exec(path)[2] || ''; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; - - -exports.extname = function(path) { - return splitPathRe.exec(path)[3] || ''; -}; -}); - -require.define("__browserify_process",function(require,module,exports,__dirname,__filename,process){var process = module.exports = {}; - -process.nextTick = (function () { - var queue = []; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - - if (canPost) { - window.addEventListener('message', function (ev) { - if (ev.source === window && ev.data === 'browserify-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - } - - return function (fn) { - if (canPost) { - queue.push(fn); - window.postMessage('browserify-tick', '*'); - } - else setTimeout(fn, 0); - }; -})(); - -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; - -process.binding = function (name) { - if (name === 'evals') return (require)('vm') - else throw new Error('No such module. (Possibly not yet loaded)') -}; - -(function () { - var cwd = '/'; - var path; - process.cwd = function () { return cwd }; - process.chdir = function (dir) { - if (!path) path = require('path'); - cwd = path.resolve(dir, cwd); - }; -})(); -}); - -require.define("vm",function(require,module,exports,__dirname,__filename,process){module.exports = require("vm-browserify")}); - -require.define("/node_modules/vm-browserify/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"index.js"}}); - -require.define("/node_modules/vm-browserify/index.js",function(require,module,exports,__dirname,__filename,process){var Object_keys = function (obj) { - if (Object.keys) return Object.keys(obj) - else { - var res = []; - for (var key in obj) res.push(key) - return res; - } -}; - -var forEach = function (xs, fn) { - if (xs.forEach) return xs.forEach(fn) - else for (var i = 0; i < xs.length; i++) { - fn(xs[i], i, xs); - } -}; - -var Script = exports.Script = function NodeScript (code) { - if (!(this instanceof Script)) return new Script(code); - this.code = code; -}; - -Script.prototype.runInNewContext = function (context) { - if (!context) context = {}; - - var iframe = document.createElement('iframe'); - if (!iframe.style) iframe.style = {}; - iframe.style.display = 'none'; - - document.body.appendChild(iframe); - - var win = iframe.contentWindow; - - forEach(Object_keys(context), function (key) { - win[key] = context[key]; - }); - - if (!win.eval && win.execScript) { - // win.eval() magically appears when this is called in IE: - win.execScript('null'); - } - - var res = win.eval(this.code); - - forEach(Object_keys(win), function (key) { - context[key] = win[key]; - }); - - document.body.removeChild(iframe); - - return res; -}; - -Script.prototype.runInThisContext = function () { - return eval(this.code); // maybe... -}; - -Script.prototype.runInContext = function (context) { - // seems to be just runInNewContext on magical context objects which are - // otherwise indistinguishable from objects except plain old objects - // for the parameter segfaults node - return this.runInNewContext(context); -}; - -forEach(Object_keys(Script.prototype), function (name) { - exports[name] = Script[name] = function (code) { - var s = Script(code); - return s[name].apply(s, [].slice.call(arguments, 1)); - }; -}); - -exports.createScript = function (code) { - return exports.Script(code); -}; - -exports.createContext = Script.createContext = function (context) { - // not really sure what this one does - // seems to just make a shallow copy - var copy = {}; - if(typeof context === 'object') { - forEach(Object_keys(context), function (key) { - copy[key] = context[key]; - }); - } - return copy; -}; -}); - -require.define("/node_modules/domready/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./ready.js"}}); - -require.define("/node_modules/domready/ready.js",function(require,module,exports,__dirname,__filename,process){/*! - * domready (c) Dustin Diaz 2012 - License MIT - */ -!function (name, definition) { - if (typeof module != 'undefined') module.exports = definition() - else if (typeof define == 'function' && typeof define.amd == 'object') define(definition) - else this[name] = definition() -}('domready', function (ready) { - - var fns = [], fn, f = false - , doc = document - , testEl = doc.documentElement - , hack = testEl.doScroll - , domContentLoaded = 'DOMContentLoaded' - , addEventListener = 'addEventListener' - , onreadystatechange = 'onreadystatechange' - , readyState = 'readyState' - , loaded = /^loade|c/.test(doc[readyState]) - - function flush(f) { - loaded = 1 - while (f = fns.shift()) f() - } - - doc[addEventListener] && doc[addEventListener](domContentLoaded, fn = function () { - doc.removeEventListener(domContentLoaded, fn, f) - flush() - }, f) - - - hack && doc.attachEvent(onreadystatechange, fn = function () { - if (/^c/.test(doc[readyState])) { - doc.detachEvent(onreadystatechange, fn) - flush() - } - }) - - return (ready = hack ? - function (fn) { - self != top ? - loaded ? fn() : fns.push(fn) : - function () { - try { - testEl.doScroll('left') - } catch (e) { - return setTimeout(function() { ready(fn) }, 50) - } - fn() - }() - } : - function (fn) { - loaded ? fn() : fns.push(fn) - }) -})}); - -require.define("/node_modules/shoe/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"index.js","browserify":"browser.js"}}); - -require.define("/node_modules/shoe/browser.js",function(require,module,exports,__dirname,__filename,process){var Stream = require('stream'); -var sockjs = require('sockjs-client'); - -module.exports = function (uri, cb) { - if (/^\/\/[^\/]+\//.test(uri)) { - uri = window.location.protocol + uri; - } - else if (!/^https?:\/\//.test(uri)) { - uri = window.location.protocol + '//' - + window.location.host - + (/^\//.test(uri) ? uri : '/' + uri) - ; - } - - var stream = new Stream; - stream.readable = true; - stream.writable = true; - - var ready = false; - var buffer = []; - - var sock = sockjs(uri); - stream.sock = sock; - - stream.write = function (msg) { - if (!ready || buffer.length) buffer.push(msg) - else sock.send(msg) - }; - stream.end = function (msg) { - if (msg !== undefined) stream.write(msg); - if (!ready) { - stream._ended = true; - return; - } - stream.writable = false; - sock.close(); - }; - - stream.destroy = function () { - stream._ended = true; - stream.writable = stream.readable = false; - buffer.length = 0 - sock.close(); - } - - sock.onopen = function () { - if (typeof cb === 'function') cb(); - ready = true; - buffer.forEach(function (msg) { - sock.send(msg); - }); - buffer = []; - stream.emit('connect') - if (stream._ended) stream.end(); - }; - sock.onmessage = function (e) { - stream.emit('data', e.data); - }; - sock.onclose = function () { - stream.emit('end'); - stream.writable = false; - stream.readable = false; - }; - - return stream; -}; -}); - -require.define("stream",function(require,module,exports,__dirname,__filename,process){var events = require('events'); -var util = require('util'); - -function Stream() { - events.EventEmitter.call(this); -} -util.inherits(Stream, events.EventEmitter); -module.exports = Stream; -// Backwards-compat with node 0.4.x -Stream.Stream = Stream; - -Stream.prototype.pipe = function(dest, options) { - var source = this; - - function ondata(chunk) { - if (dest.writable) { - if (false === dest.write(chunk) && source.pause) { - source.pause(); - } - } - } - - source.on('data', ondata); - - function ondrain() { - if (source.readable && source.resume) { - source.resume(); - } - } - - dest.on('drain', ondrain); - - // If the 'end' option is not supplied, dest.end() will be called when - // source gets the 'end' or 'close' events. Only dest.end() once, and - // only when all sources have ended. - if (!dest._isStdio && (!options || options.end !== false)) { - dest._pipeCount = dest._pipeCount || 0; - dest._pipeCount++; - - source.on('end', onend); - source.on('close', onclose); - } - - var didOnEnd = false; - function onend() { - if (didOnEnd) return; - didOnEnd = true; - - dest._pipeCount--; - - // remove the listeners - cleanup(); - - if (dest._pipeCount > 0) { - // waiting for other incoming streams to end. - return; - } - - dest.end(); - } - - - function onclose() { - if (didOnEnd) return; - didOnEnd = true; - - dest._pipeCount--; - - // remove the listeners - cleanup(); - - if (dest._pipeCount > 0) { - // waiting for other incoming streams to end. - return; - } - - dest.destroy(); - } - - // don't leave dangling pipes when there are errors. - function onerror(er) { - cleanup(); - if (this.listeners('error').length === 0) { - throw er; // Unhandled stream error in pipe. - } - } - - source.on('error', onerror); - dest.on('error', onerror); - - // remove all the event listeners that were added. - function cleanup() { - source.removeListener('data', ondata); - dest.removeListener('drain', ondrain); - - source.removeListener('end', onend); - source.removeListener('close', onclose); - - source.removeListener('error', onerror); - dest.removeListener('error', onerror); - - source.removeListener('end', cleanup); - source.removeListener('close', cleanup); - - dest.removeListener('end', cleanup); - dest.removeListener('close', cleanup); - } - - source.on('end', cleanup); - source.on('close', cleanup); - - dest.on('end', cleanup); - dest.on('close', cleanup); - - dest.emit('pipe', source); - - // Allow for unix-like usage: A.pipe(B).pipe(C) - return dest; -}; -}); - -require.define("events",function(require,module,exports,__dirname,__filename,process){if (!process.EventEmitter) process.EventEmitter = function () {}; - -var EventEmitter = exports.EventEmitter = process.EventEmitter; -var isArray = typeof Array.isArray === 'function' - ? Array.isArray - : function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]' - } -; - -// By default EventEmitters will print a warning if more than -// 10 listeners are added to it. This is a useful default which -// helps finding memory leaks. -// -// Obviously not all Emitters should be limited to 10. This function allows -// that to be increased. Set to zero for unlimited. -var defaultMaxListeners = 10; -EventEmitter.prototype.setMaxListeners = function(n) { - if (!this._events) this._events = {}; - this._events.maxListeners = n; -}; - - -EventEmitter.prototype.emit = function(type) { - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events || !this._events.error || - (isArray(this._events.error) && !this._events.error.length)) - { - if (arguments[1] instanceof Error) { - throw arguments[1]; // Unhandled 'error' event - } else { - throw new Error("Uncaught, unspecified 'error' event."); - } - return false; - } - } - - if (!this._events) return false; - var handler = this._events[type]; - if (!handler) return false; - - if (typeof handler == 'function') { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - var args = Array.prototype.slice.call(arguments, 1); - handler.apply(this, args); - } - return true; - - } else if (isArray(handler)) { - var args = Array.prototype.slice.call(arguments, 1); - - var listeners = handler.slice(); - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - return true; - - } else { - return false; - } -}; - -// EventEmitter is defined in src/node_events.cc -// EventEmitter.prototype.emit() is also defined there. -EventEmitter.prototype.addListener = function(type, listener) { - if ('function' !== typeof listener) { - throw new Error('addListener only takes instances of Function'); - } - - if (!this._events) this._events = {}; - - // To avoid recursion in the case that type == "newListeners"! Before - // adding it to the listeners, first emit "newListeners". - this.emit('newListener', type, listener); - - if (!this._events[type]) { - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - } else if (isArray(this._events[type])) { - - // Check for listener leak - if (!this._events[type].warned) { - var m; - if (this._events.maxListeners !== undefined) { - m = this._events.maxListeners; - } else { - m = defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.', - this._events[type].length); - console.trace(); - } - } - - // If we've already got an array, just append. - this._events[type].push(listener); - } else { - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - } - - return this; -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; - -EventEmitter.prototype.once = function(type, listener) { - var self = this; - self.on(type, function g() { - self.removeListener(type, g); - listener.apply(this, arguments); - }); - - return this; -}; - -EventEmitter.prototype.removeListener = function(type, listener) { - if ('function' !== typeof listener) { - throw new Error('removeListener only takes instances of Function'); - } - - // does not use listeners(), so no side effect of creating _events[type] - if (!this._events || !this._events[type]) return this; - - var list = this._events[type]; - - if (isArray(list)) { - var i = list.indexOf(listener); - if (i < 0) return this; - list.splice(i, 1); - if (list.length == 0) - delete this._events[type]; - } else if (this._events[type] === listener) { - delete this._events[type]; - } - - return this; -}; - -EventEmitter.prototype.removeAllListeners = function(type) { - // does not use listeners(), so no side effect of creating _events[type] - if (type && this._events && this._events[type]) this._events[type] = null; - return this; -}; - -EventEmitter.prototype.listeners = function(type) { - if (!this._events) this._events = {}; - if (!this._events[type]) this._events[type] = []; - if (!isArray(this._events[type])) { - this._events[type] = [this._events[type]]; - } - return this._events[type]; -}; -}); - -require.define("util",function(require,module,exports,__dirname,__filename,process){var events = require('events'); - -exports.print = function () {}; -exports.puts = function () {}; -exports.debug = function() {}; - -exports.inspect = function(obj, showHidden, depth, colors) { - var seen = []; - - var stylize = function(str, styleType) { - // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics - var styles = - { 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'inverse' : [7, 27], - 'white' : [37, 39], - 'grey' : [90, 39], - 'black' : [30, 39], - 'blue' : [34, 39], - 'cyan' : [36, 39], - 'green' : [32, 39], - 'magenta' : [35, 39], - 'red' : [31, 39], - 'yellow' : [33, 39] }; - - var style = - { 'special': 'cyan', - 'number': 'blue', - 'boolean': 'yellow', - 'undefined': 'grey', - 'null': 'bold', - 'string': 'green', - 'date': 'magenta', - // "name": intentionally not styling - 'regexp': 'red' }[styleType]; - - if (style) { - return '\033[' + styles[style][0] + 'm' + str + - '\033[' + styles[style][1] + 'm'; - } else { - return str; - } - }; - if (! colors) { - stylize = function(str, styleType) { return str; }; - } - - function format(value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (value && typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value !== exports && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - return value.inspect(recurseTimes); - } - - // Primitive types cannot have properties - switch (typeof value) { - case 'undefined': - return stylize('undefined', 'undefined'); - - case 'string': - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return stylize(simple, 'string'); - - case 'number': - return stylize('' + value, 'number'); - - case 'boolean': - return stylize('' + value, 'boolean'); - } - // For some reason typeof null is "object", so special case here. - if (value === null) { - return stylize('null', 'null'); - } - - // Look up the keys of the object. - var visible_keys = Object_keys(value); - var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; - - // Functions without properties can be shortcutted. - if (typeof value === 'function' && keys.length === 0) { - if (isRegExp(value)) { - return stylize('' + value, 'regexp'); - } else { - var name = value.name ? ': ' + value.name : ''; - return stylize('[Function' + name + ']', 'special'); - } - } - - // Dates without properties can be shortcutted - if (isDate(value) && keys.length === 0) { - return stylize(value.toUTCString(), 'date'); - } - - var base, type, braces; - // Determine the object type - if (isArray(value)) { - type = 'Array'; - braces = ['[', ']']; - } else { - type = 'Object'; - braces = ['{', '}']; - } - - // Make functions say that they are functions - if (typeof value === 'function') { - var n = value.name ? ': ' + value.name : ''; - base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; - } else { - base = ''; - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + value.toUTCString(); - } - - if (keys.length === 0) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return stylize('' + value, 'regexp'); - } else { - return stylize('[Object]', 'special'); - } - } - - seen.push(value); - - var output = keys.map(function(key) { - var name, str; - if (value.__lookupGetter__) { - if (value.__lookupGetter__(key)) { - if (value.__lookupSetter__(key)) { - str = stylize('[Getter/Setter]', 'special'); - } else { - str = stylize('[Getter]', 'special'); - } - } else { - if (value.__lookupSetter__(key)) { - str = stylize('[Setter]', 'special'); - } - } - } - if (visible_keys.indexOf(key) < 0) { - name = '[' + key + ']'; - } - if (!str) { - if (seen.indexOf(value[key]) < 0) { - if (recurseTimes === null) { - str = format(value[key]); - } else { - str = format(value[key], recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (isArray(value)) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = stylize('[Circular]', 'special'); - } - } - if (typeof name === 'undefined') { - if (type === 'Array' && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = stylize(name, 'string'); - } - } - - return name + ': ' + str; - }); - - seen.pop(); - - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.length + 1; - }, 0); - - if (length > 50) { - output = braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - - } else { - output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - return output; - } - return format(obj, (typeof depth === 'undefined' ? 2 : depth)); -}; - - -function isArray(ar) { - return ar instanceof Array || - Array.isArray(ar) || - (ar && ar !== Object.prototype && isArray(ar.__proto__)); -} - - -function isRegExp(re) { - return re instanceof RegExp || - (typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'); -} - - -function isDate(d) { - if (d instanceof Date) return true; - if (typeof d !== 'object') return false; - var properties = Date.prototype && Object_getOwnPropertyNames(Date.prototype); - var proto = d.__proto__ && Object_getOwnPropertyNames(d.__proto__); - return JSON.stringify(proto) === JSON.stringify(properties); -} - -function pad(n) { - return n < 10 ? '0' + n.toString(10) : n.toString(10); -} - -var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', - 'Oct', 'Nov', 'Dec']; - -// 26 Feb 16:19:34 -function timestamp() { - var d = new Date(); - var time = [pad(d.getHours()), - pad(d.getMinutes()), - pad(d.getSeconds())].join(':'); - return [d.getDate(), months[d.getMonth()], time].join(' '); -} - -exports.log = function (msg) {}; - -exports.pump = null; - -var Object_keys = Object.keys || function (obj) { - var res = []; - for (var key in obj) res.push(key); - return res; -}; - -var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { - var res = []; - for (var key in obj) { - if (Object.hasOwnProperty.call(obj, key)) res.push(key); - } - return res; -}; - -var Object_create = Object.create || function (prototype, properties) { - // from es5-shim - var object; - if (prototype === null) { - object = { '__proto__' : null }; - } - else { - if (typeof prototype !== 'object') { - throw new TypeError( - 'typeof prototype[' + (typeof prototype) + '] != \'object\'' - ); - } - var Type = function () {}; - Type.prototype = prototype; - object = new Type(); - object.__proto__ = prototype; - } - if (typeof properties !== 'undefined' && Object.defineProperties) { - Object.defineProperties(object, properties); - } - return object; -}; - -exports.inherits = function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object_create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); -}; -}); - -require.define("/node_modules/shoe/node_modules/sockjs-client/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"sockjs.js"}}); - -require.define("/node_modules/shoe/node_modules/sockjs-client/sockjs.js",function(require,module,exports,__dirname,__filename,process){/* SockJS client, version 0.3.1.7.ga67f.dirty, http://sockjs.org, MIT License - -Copyright (c) 2011-2012 VMware, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -// JSON2 by Douglas Crockford (minified). -var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c 1) { - this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) ); - } else { - delete this._listeners[eventType]; - } - return; - } - return; -}; - -REventTarget.prototype.dispatchEvent = function (event) { - var t = event.type; - var args = Array.prototype.slice.call(arguments, 0); - if (this['on'+t]) { - this['on'+t].apply(this, args); - } - if (this._listeners && t in this._listeners) { - for(var i=0; i < this._listeners[t].length; i++) { - this._listeners[t][i].apply(this, args); - } - } -}; -// [*] End of lib/reventtarget.js - - -// [*] Including lib/simpleevent.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var SimpleEvent = function(type, obj) { - this.type = type; - if (typeof obj !== 'undefined') { - for(var k in obj) { - if (!obj.hasOwnProperty(k)) continue; - this[k] = obj[k]; - } - } -}; - -SimpleEvent.prototype.toString = function() { - var r = []; - for(var k in this) { - if (!this.hasOwnProperty(k)) continue; - var v = this[k]; - if (typeof v === 'function') v = '[function]'; - r.push(k + '=' + v); - } - return 'SimpleEvent(' + r.join(', ') + ')'; -}; -// [*] End of lib/simpleevent.js - - -// [*] Including lib/eventemitter.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var EventEmitter = function(events) { - this.events = events || []; -}; -EventEmitter.prototype.emit = function(type) { - var that = this; - var args = Array.prototype.slice.call(arguments, 1); - if (!that.nuked && that['on'+type]) { - that['on'+type].apply(that, args); - } - if (utils.arrIndexOf(that.events, type) === -1) { - utils.log('Event ' + JSON.stringify(type) + - ' not listed ' + JSON.stringify(that.events) + - ' in ' + that); - } -}; - -EventEmitter.prototype.nuke = function(type) { - var that = this; - that.nuked = true; - for(var i=0; i= 3000 && code <= 4999); -}; - -// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/ -// and RFC 2988. -utils.countRTO = function (rtt) { - var rto; - if (rtt > 100) { - rto = 3 * rtt; // rto > 300msec - } else { - rto = rtt + 200; // 200msec < rto <= 300msec - } - return rto; -} - -utils.log = function() { - if (_window.console && console.log && console.log.apply) { - console.log.apply(console, arguments); - } -}; - -utils.bind = function(fun, that) { - if (fun.bind) { - return fun.bind(that); - } else { - return function() { - return fun.apply(that, arguments); - }; - } -}; - -utils.flatUrl = function(url) { - return url.indexOf('?') === -1 && url.indexOf('#') === -1; -}; - -utils.amendUrl = function(url) { - var dl = _document.location; - if (!url) { - throw new Error('Wrong url for SockJS'); - } - if (!utils.flatUrl(url)) { - throw new Error('Only basic urls are supported in SockJS'); - } - - // '//abc' --> 'http://abc' - if (url.indexOf('//') === 0) { - url = dl.protocol + url; - } - // '/abc' --> 'http://localhost:80/abc' - if (url.indexOf('/') === 0) { - url = dl.protocol + '//' + dl.host + url; - } - // strip trailing slashes - url = url.replace(/[/]+$/,''); - return url; -}; - -// IE doesn't support [].indexOf. -utils.arrIndexOf = function(arr, obj){ - for(var i=0; i < arr.length; i++){ - if(arr[i] === obj){ - return i; - } - } - return -1; -}; - -utils.arrSkip = function(arr, obj) { - var idx = utils.arrIndexOf(arr, obj); - if (idx === -1) { - return arr.slice(); - } else { - var dst = arr.slice(0, idx); - return dst.concat(arr.slice(idx+1)); - } -}; - -// Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df -utils.isArray = Array.isArray || function(value) { - return {}.toString.call(value).indexOf('Array') >= 0 -}; - -utils.delay = function(t, fun) { - if(typeof t === 'function') { - fun = t; - t = 0; - } - return setTimeout(fun, t); -}; - - -// Chars worth escaping, as defined by Douglas Crockford: -// https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196 -var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - json_lookup = { -"\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003", -"\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007", -"\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r", -"\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011", -"\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015", -"\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019", -"\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d", -"\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\", -"\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082", -"\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086", -"\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a", -"\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e", -"\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092", -"\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096", -"\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a", -"\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e", -"\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601", -"\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f", -"\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d", -"\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029", -"\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d", -"\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061", -"\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065", -"\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069", -"\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d", -"\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0", -"\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4", -"\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8", -"\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc", -"\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"}; - -// Some extra characters that Chrome gets wrong, and substitutes with -// something else on the wire. -var extra_escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g, - extra_lookup; - -// JSON Quote string. Use native implementation when possible. -var JSONQuote = (JSON && JSON.stringify) || function(string) { - json_escapable.lastIndex = 0; - if (json_escapable.test(string)) { - string = string.replace(json_escapable, function(a) { - return json_lookup[a]; - }); - } - return '"' + string + '"'; -}; - -// This may be quite slow, so let's delay until user actually uses bad -// characters. -var unroll_lookup = function(escapable) { - var i; - var unrolled = {} - var c = [] - for(i=0; i<65536; i++) { - c.push( String.fromCharCode(i) ); - } - escapable.lastIndex = 0; - c.join('').replace(escapable, function (a) { - unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - return ''; - }); - escapable.lastIndex = 0; - return unrolled; -}; - -// Quote string, also taking care of unicode characters that browsers -// often break. Especially, take care of unicode surrogates: -// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates -utils.quote = function(string) { - var quoted = JSONQuote(string); - - // In most cases this should be very fast and good enough. - extra_escapable.lastIndex = 0; - if(!extra_escapable.test(quoted)) { - return quoted; - } - - if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable); - - return quoted.replace(extra_escapable, function(a) { - return extra_lookup[a]; - }); -} - -var _all_protocols = ['websocket', - 'xdr-streaming', - 'xhr-streaming', - 'iframe-eventsource', - 'iframe-htmlfile', - 'xdr-polling', - 'xhr-polling', - 'iframe-xhr-polling', - 'jsonp-polling']; - -utils.probeProtocols = function() { - var probed = {}; - for(var i=0; i<_all_protocols.length; i++) { - var protocol = _all_protocols[i]; - // User can have a typo in protocol name. - probed[protocol] = SockJS[protocol] && - SockJS[protocol].enabled(); - } - return probed; -}; - -utils.detectProtocols = function(probed, protocols_whitelist, info) { - var pe = {}, - protocols = []; - if (!protocols_whitelist) protocols_whitelist = _all_protocols; - for(var i=0; i 0) { - maybe_push(protos); - } - } - } - - // 1. Websocket - if (info.websocket !== false) { - maybe_push(['websocket']); - } - - // 2. Streaming - if (pe['xhr-streaming'] && !info.null_origin) { - protocols.push('xhr-streaming'); - } else { - if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) { - protocols.push('xdr-streaming'); - } else { - maybe_push(['iframe-eventsource', - 'iframe-htmlfile']); - } - } - - // 3. Polling - if (pe['xhr-polling'] && !info.null_origin) { - protocols.push('xhr-polling'); - } else { - if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) { - protocols.push('xdr-polling'); - } else { - maybe_push(['iframe-xhr-polling', - 'jsonp-polling']); - } - } - return protocols; -} -// [*] End of lib/utils.js - - -// [*] Including lib/dom.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -// May be used by htmlfile jsonp and transports. -var MPrefix = '_sockjs_global'; -utils.createHook = function() { - var window_id = 'a' + utils.random_string(8); - if (!(MPrefix in _window)) { - var map = {}; - _window[MPrefix] = function(window_id) { - if (!(window_id in map)) { - map[window_id] = { - id: window_id, - del: function() {delete map[window_id];} - }; - } - return map[window_id]; - } - } - return _window[MPrefix](window_id); -}; - - - -utils.attachMessage = function(listener) { - utils.attachEvent('message', listener); -}; -utils.attachEvent = function(event, listener) { - if (typeof _window.addEventListener !== 'undefined') { - _window.addEventListener(event, listener, false); - } else { - // IE quirks. - // According to: http://stevesouders.com/misc/test-postmessage.php - // the message gets delivered only to 'document', not 'window'. - _document.attachEvent("on" + event, listener); - // I get 'window' for ie8. - _window.attachEvent("on" + event, listener); - } -}; - -utils.detachMessage = function(listener) { - utils.detachEvent('message', listener); -}; -utils.detachEvent = function(event, listener) { - if (typeof _window.addEventListener !== 'undefined') { - _window.removeEventListener(event, listener, false); - } else { - _document.detachEvent("on" + event, listener); - _window.detachEvent("on" + event, listener); - } -}; - - -var on_unload = {}; -// Things registered after beforeunload are to be called immediately. -var after_unload = false; - -var trigger_unload_callbacks = function() { - for(var ref in on_unload) { - on_unload[ref](); - delete on_unload[ref]; - }; -}; - -var unload_triggered = function() { - if(after_unload) return; - after_unload = true; - trigger_unload_callbacks(); -}; - -// Onbeforeunload alone is not reliable. We could use only 'unload' -// but it's not working in opera within an iframe. Let's use both. -utils.attachEvent('beforeunload', unload_triggered); -utils.attachEvent('unload', unload_triggered); - -utils.unload_add = function(listener) { - var ref = utils.random_string(8); - on_unload[ref] = listener; - if (after_unload) { - utils.delay(trigger_unload_callbacks); - } - return ref; -}; -utils.unload_del = function(ref) { - if (ref in on_unload) - delete on_unload[ref]; -}; - - -utils.createIframe = function (iframe_url, error_callback) { - var iframe = _document.createElement('iframe'); - var tref, unload_ref; - var unattach = function() { - clearTimeout(tref); - // Explorer had problems with that. - try {iframe.onload = null;} catch (x) {} - iframe.onerror = null; - }; - var cleanup = function() { - if (iframe) { - unattach(); - // This timeout makes chrome fire onbeforeunload event - // within iframe. Without the timeout it goes straight to - // onunload. - setTimeout(function() { - if(iframe) { - iframe.parentNode.removeChild(iframe); - } - iframe = null; - }, 0); - utils.unload_del(unload_ref); - } - }; - var onerror = function(r) { - if (iframe) { - cleanup(); - error_callback(r); - } - }; - var post = function(msg, origin) { - try { - // When the iframe is not loaded, IE raises an exception - // on 'contentWindow'. - if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage(msg, origin); - } - } catch (x) {}; - }; - - iframe.src = iframe_url; - iframe.style.display = 'none'; - iframe.style.position = 'absolute'; - iframe.onerror = function(){onerror('onerror');}; - iframe.onload = function() { - // `onload` is triggered before scripts on the iframe are - // executed. Give it few seconds to actually load stuff. - clearTimeout(tref); - tref = setTimeout(function(){onerror('onload timeout');}, 2000); - }; - _document.body.appendChild(iframe); - tref = setTimeout(function(){onerror('timeout');}, 15000); - unload_ref = utils.unload_add(cleanup); - return { - post: post, - cleanup: cleanup, - loaded: unattach - }; -}; - -utils.createHtmlfile = function (iframe_url, error_callback) { - var doc = new ActiveXObject('htmlfile'); - var tref, unload_ref; - var iframe; - var unattach = function() { - clearTimeout(tref); - }; - var cleanup = function() { - if (doc) { - unattach(); - utils.unload_del(unload_ref); - iframe.parentNode.removeChild(iframe); - iframe = doc = null; - CollectGarbage(); - } - }; - var onerror = function(r) { - if (doc) { - cleanup(); - error_callback(r); - } - }; - var post = function(msg, origin) { - try { - // When the iframe is not loaded, IE raises an exception - // on 'contentWindow'. - if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage(msg, origin); - } - } catch (x) {}; - }; - - doc.open(); - doc.write('' + - 'document.domain="' + document.domain + '";' + - ''); - doc.close(); - doc.parentWindow[WPrefix] = _window[WPrefix]; - var c = doc.createElement('div'); - doc.body.appendChild(c); - iframe = doc.createElement('iframe'); - c.appendChild(iframe); - iframe.src = iframe_url; - tref = setTimeout(function(){onerror('timeout');}, 15000); - unload_ref = utils.unload_add(cleanup); - return { - post: post, - cleanup: cleanup, - loaded: unattach - }; -}; -// [*] End of lib/dom.js - - -// [*] Including lib/dom2.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var AbstractXHRObject = function(){}; -AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']); - -AbstractXHRObject.prototype._start = function(method, url, payload, opts) { - var that = this; - - try { - that.xhr = new XMLHttpRequest(); - } catch(x) {}; - - if (!that.xhr) { - try { - that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP'); - } catch(x) {}; - } - if (_window.ActiveXObject || _window.XDomainRequest) { - // IE8 caches even POSTs - url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); - } - - // Explorer tends to keep connection open, even after the - // tab gets closed: http://bugs.jquery.com/ticket/5280 - that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); - try { - that.xhr.open(method, url, true); - } catch(e) { - // IE raises an exception on wrong port. - that.emit('finish', 0, ''); - that._cleanup(); - return; - }; - - if (!opts || !opts.no_credentials) { - // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest : - // "This never affects same-site requests." - that.xhr.withCredentials = 'true'; - } - if (opts && opts.headers) { - for(var key in opts.headers) { - that.xhr.setRequestHeader(key, opts.headers[key]); - } - } - - that.xhr.onreadystatechange = function() { - if (that.xhr) { - var x = that.xhr; - switch (x.readyState) { - case 3: - // IE doesn't like peeking into responseText or status - // on Microsoft.XMLHTTP and readystate=3 - try { - var status = x.status; - var text = x.responseText; - } catch (x) {}; - // IE does return readystate == 3 for 404 answers. - if (text && text.length > 0) { - that.emit('chunk', status, text); - } - break; - case 4: - that.emit('finish', x.status, x.responseText); - that._cleanup(false); - break; - } - } - }; - that.xhr.send(payload); -}; - -AbstractXHRObject.prototype._cleanup = function(abort) { - var that = this; - if (!that.xhr) return; - utils.unload_del(that.unload_ref); - - // IE needs this field to be a function - that.xhr.onreadystatechange = function(){}; - - if (abort) { - try { - that.xhr.abort(); - } catch(x) {}; - } - that.unload_ref = that.xhr = null; -}; - -AbstractXHRObject.prototype.close = function() { - var that = this; - that.nuke(); - that._cleanup(true); -}; - -var XHRCorsObject = utils.XHRCorsObject = function() { - var that = this, args = arguments; - utils.delay(function(){that._start.apply(that, args);}); -}; -XHRCorsObject.prototype = new AbstractXHRObject(); - -var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) { - var that = this; - utils.delay(function(){ - that._start(method, url, payload, { - no_credentials: true - }); - }); -}; -XHRLocalObject.prototype = new AbstractXHRObject(); - - - -// References: -// http://ajaxian.com/archives/100-line-ajax-wrapper -// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx -var XDRObject = utils.XDRObject = function(method, url, payload) { - var that = this; - utils.delay(function(){that._start(method, url, payload);}); -}; -XDRObject.prototype = new EventEmitter(['chunk', 'finish']); -XDRObject.prototype._start = function(method, url, payload) { - var that = this; - var xdr = new XDomainRequest(); - // IE caches even POSTs - url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); - - var onerror = xdr.ontimeout = xdr.onerror = function() { - that.emit('finish', 0, ''); - that._cleanup(false); - }; - xdr.onprogress = function() { - that.emit('chunk', 200, xdr.responseText); - }; - xdr.onload = function() { - that.emit('finish', 200, xdr.responseText); - that._cleanup(false); - }; - that.xdr = xdr; - that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); - try { - // Fails with AccessDenied if port number is bogus - that.xdr.open(method, url); - that.xdr.send(payload); - } catch(x) { - onerror(); - } -}; - -XDRObject.prototype._cleanup = function(abort) { - var that = this; - if (!that.xdr) return; - utils.unload_del(that.unload_ref); - - that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress = - that.xdr.onload = null; - if (abort) { - try { - that.xdr.abort(); - } catch(x) {}; - } - that.unload_ref = that.xdr = null; -}; - -XDRObject.prototype.close = function() { - var that = this; - that.nuke(); - that._cleanup(true); -}; - -// 1. Is natively via XHR -// 2. Is natively via XDR -// 3. Nope, but postMessage is there so it should work via the Iframe. -// 4. Nope, sorry. -utils.isXHRCorsCapable = function() { - if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) { - return 1; - } - // XDomainRequest doesn't work if page is served from file:// - if (_window.XDomainRequest && _document.domain) { - return 2; - } - if (IframeTransport.enabled()) { - return 3; - } - return 4; -}; -// [*] End of lib/dom2.js - - -// [*] Including lib/sockjs.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var SockJS = function(url, dep_protocols_whitelist, options) { - if (this === window) { - // makes `new` optional - return new SockJS(url, dep_protocols_whitelist, options); - } - - var that = this, protocols_whitelist; - that._options = {devel: false, debug: false, protocols_whitelist: [], - info: undefined, rtt: undefined}; - if (options) { - utils.objectExtend(that._options, options); - } - that._base_url = utils.amendUrl(url); - that._server = that._options.server || utils.random_number_string(1000); - if (that._options.protocols_whitelist && - that._options.protocols_whitelist.length) { - protocols_whitelist = that._options.protocols_whitelist; - } else { - // Deprecated API - if (typeof dep_protocols_whitelist === 'string' && - dep_protocols_whitelist.length > 0) { - protocols_whitelist = [dep_protocols_whitelist]; - } else if (utils.isArray(dep_protocols_whitelist)) { - protocols_whitelist = dep_protocols_whitelist - } else { - protocols_whitelist = null; - } - if (protocols_whitelist) { - that._debug('Deprecated API: Use "protocols_whitelist" option ' + - 'instead of supplying protocol list as a second ' + - 'parameter to SockJS constructor.'); - } - } - that._protocols = []; - that.protocol = null; - that.readyState = SockJS.CONNECTING; - that._ir = createInfoReceiver(that._base_url); - that._ir.onfinish = function(info, rtt) { - that._ir = null; - if (info) { - if (that._options.info) { - // Override if user supplies the option - info = utils.objectExtend(info, that._options.info); - } - if (that._options.rtt) { - rtt = that._options.rtt; - } - that._applyInfo(info, rtt, protocols_whitelist); - that._didClose(); - } else { - that._didClose(1002, 'Can\'t connect to server', true); - } - }; -}; -// Inheritance -SockJS.prototype = new REventTarget(); - -SockJS.version = "0.3.1.7.ga67f.dirty"; - -SockJS.CONNECTING = 0; -SockJS.OPEN = 1; -SockJS.CLOSING = 2; -SockJS.CLOSED = 3; - -SockJS.prototype._debug = function() { - if (this._options.debug) - utils.log.apply(utils, arguments); -}; - -SockJS.prototype._dispatchOpen = function() { - var that = this; - if (that.readyState === SockJS.CONNECTING) { - if (that._transport_tref) { - clearTimeout(that._transport_tref); - that._transport_tref = null; - } - that.readyState = SockJS.OPEN; - that.dispatchEvent(new SimpleEvent("open")); - } else { - // The server might have been restarted, and lost track of our - // connection. - that._didClose(1006, "Server lost session"); - } -}; - -SockJS.prototype._dispatchMessage = function(data) { - var that = this; - if (that.readyState !== SockJS.OPEN) - return; - that.dispatchEvent(new SimpleEvent("message", {data: data})); -}; - -SockJS.prototype._dispatchHeartbeat = function(data) { - var that = this; - if (that.readyState !== SockJS.OPEN) - return; - that.dispatchEvent(new SimpleEvent('heartbeat', {})); -}; - -SockJS.prototype._didClose = function(code, reason, force) { - var that = this; - if (that.readyState !== SockJS.CONNECTING && - that.readyState !== SockJS.OPEN && - that.readyState !== SockJS.CLOSING) - throw new Error('INVALID_STATE_ERR'); - if (that._ir) { - that._ir.nuke(); - that._ir = null; - } - - if (that._transport) { - that._transport.doCleanup(); - that._transport = null; - } - - var close_event = new SimpleEvent("close", { - code: code, - reason: reason, - wasClean: utils.userSetCode(code)}); - - if (!utils.userSetCode(code) && - that.readyState === SockJS.CONNECTING && !force) { - if (that._try_next_protocol(close_event)) { - return; - } - close_event = new SimpleEvent("close", {code: 2000, - reason: "All transports failed", - wasClean: false, - last_event: close_event}); - } - that.readyState = SockJS.CLOSED; - - utils.delay(function() { - that.dispatchEvent(close_event); - }); -}; - -SockJS.prototype._didMessage = function(data) { - var that = this; - var type = data.slice(0, 1); - switch(type) { - case 'o': - that._dispatchOpen(); - break; - case 'a': - var payload = JSON.parse(data.slice(1) || '[]'); - for(var i=0; i < payload.length; i++){ - that._dispatchMessage(payload[i]); - } - break; - case 'm': - var payload = JSON.parse(data.slice(1) || 'null'); - that._dispatchMessage(payload); - break; - case 'c': - var payload = JSON.parse(data.slice(1) || '[]'); - that._didClose(payload[0], payload[1]); - break; - case 'h': - that._dispatchHeartbeat(); - break; - } -}; - -SockJS.prototype._try_next_protocol = function(close_event) { - var that = this; - if (that.protocol) { - that._debug('Closed transport:', that.protocol, ''+close_event); - that.protocol = null; - } - if (that._transport_tref) { - clearTimeout(that._transport_tref); - that._transport_tref = null; - } - - while(1) { - var protocol = that.protocol = that._protocols.shift(); - if (!protocol) { - return false; - } - // Some protocols require access to `body`, what if were in - // the `head`? - if (SockJS[protocol] && - SockJS[protocol].need_body === true && - (!_document.body || - (typeof _document.readyState !== 'undefined' - && _document.readyState !== 'complete'))) { - that._protocols.unshift(protocol); - that.protocol = 'waiting-for-load'; - utils.attachEvent('load', function(){ - that._try_next_protocol(); - }); - return true; - } - - if (!SockJS[protocol] || - !SockJS[protocol].enabled(that._options)) { - that._debug('Skipping transport:', protocol); - } else { - var roundTrips = SockJS[protocol].roundTrips || 1; - var to = ((that._options.rto || 0) * roundTrips) || 5000; - that._transport_tref = utils.delay(to, function() { - if (that.readyState === SockJS.CONNECTING) { - // I can't understand how it is possible to run - // this timer, when the state is CLOSED, but - // apparently in IE everythin is possible. - that._didClose(2007, "Transport timeouted"); - } - }); - - var connid = utils.random_string(8); - var trans_url = that._base_url + '/' + that._server + '/' + connid; - that._debug('Opening transport:', protocol, ' url:'+trans_url, - ' RTO:'+that._options.rto); - that._transport = new SockJS[protocol](that, trans_url, - that._base_url); - return true; - } - } -}; - -SockJS.prototype.close = function(code, reason) { - var that = this; - if (code && !utils.userSetCode(code)) - throw new Error("INVALID_ACCESS_ERR"); - if(that.readyState !== SockJS.CONNECTING && - that.readyState !== SockJS.OPEN) { - return false; - } - that.readyState = SockJS.CLOSING; - that._didClose(code || 1000, reason || "Normal closure"); - return true; -}; - -SockJS.prototype.send = function(data) { - var that = this; - if (that.readyState === SockJS.CONNECTING) - throw new Error('INVALID_STATE_ERR'); - if (that.readyState === SockJS.OPEN) { - that._transport.doSend(utils.quote('' + data)); - } - return true; -}; - -SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) { - var that = this; - that._options.info = info; - that._options.rtt = rtt; - that._options.rto = utils.countRTO(rtt); - that._options.info.null_origin = !_document.domain; - var probed = utils.probeProtocols(); - that._protocols = utils.detectProtocols(probed, protocols_whitelist, info); -}; -// [*] End of lib/sockjs.js - - -// [*] Including lib/trans-websocket.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var WebSocketTransport = SockJS.websocket = function(ri, trans_url) { - var that = this; - var url = trans_url + '/websocket'; - if (url.slice(0, 5) === 'https') { - url = 'wss' + url.slice(5); - } else { - url = 'ws' + url.slice(4); - } - that.ri = ri; - that.url = url; - var Constructor = _window.WebSocket || _window.MozWebSocket; - - that.ws = new Constructor(that.url); - that.ws.onmessage = function(e) { - that.ri._didMessage(e.data); - }; - // Firefox has an interesting bug. If a websocket connection is - // created after onbeforeunload, it stays alive even when user - // navigates away from the page. In such situation let's lie - - // let's not open the ws connection at all. See: - // https://github.com/sockjs/sockjs-client/issues/28 - // https://bugzilla.mozilla.org/show_bug.cgi?id=696085 - that.unload_ref = utils.unload_add(function(){that.ws.close()}); - that.ws.onclose = function() { - that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken")); - }; -}; - -WebSocketTransport.prototype.doSend = function(data) { - this.ws.send('[' + data + ']'); -}; - -WebSocketTransport.prototype.doCleanup = function() { - var that = this; - var ws = that.ws; - if (ws) { - ws.onmessage = ws.onclose = null; - ws.close(); - utils.unload_del(that.unload_ref); - that.unload_ref = that.ri = that.ws = null; - } -}; - -WebSocketTransport.enabled = function() { - return !!(_window.WebSocket || _window.MozWebSocket); -}; - -// In theory, ws should require 1 round trip. But in chrome, this is -// not very stable over SSL. Most likely a ws connection requires a -// separate SSL connection, in which case 2 round trips are an -// absolute minumum. -WebSocketTransport.roundTrips = 2; -// [*] End of lib/trans-websocket.js - - -// [*] Including lib/trans-sender.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var BufferedSender = function() {}; -BufferedSender.prototype.send_constructor = function(sender) { - var that = this; - that.send_buffer = []; - that.sender = sender; -}; -BufferedSender.prototype.doSend = function(message) { - var that = this; - that.send_buffer.push(message); - if (!that.send_stop) { - that.send_schedule(); - } -}; - -// For polling transports in a situation when in the message callback, -// new message is being send. If the sending connection was started -// before receiving one, it is possible to saturate the network and -// timeout due to the lack of receiving socket. To avoid that we delay -// sending messages by some small time, in order to let receiving -// connection be started beforehand. This is only a halfmeasure and -// does not fix the big problem, but it does make the tests go more -// stable on slow networks. -BufferedSender.prototype.send_schedule_wait = function() { - var that = this; - var tref; - that.send_stop = function() { - that.send_stop = null; - clearTimeout(tref); - }; - tref = utils.delay(25, function() { - that.send_stop = null; - that.send_schedule(); - }); -}; - -BufferedSender.prototype.send_schedule = function() { - var that = this; - if (that.send_buffer.length > 0) { - var payload = '[' + that.send_buffer.join(',') + ']'; - that.send_stop = that.sender(that.trans_url, - payload, - function() { - that.send_stop = null; - that.send_schedule_wait(); - }); - that.send_buffer = []; - } -}; - -BufferedSender.prototype.send_destructor = function() { - var that = this; - if (that._send_stop) { - that._send_stop(); - } - that._send_stop = null; -}; - -var jsonPGenericSender = function(url, payload, callback) { - var that = this; - - if (!('_send_form' in that)) { - var form = that._send_form = _document.createElement('form'); - var area = that._send_area = _document.createElement('textarea'); - area.name = 'd'; - form.style.display = 'none'; - form.style.position = 'absolute'; - form.method = 'POST'; - form.enctype = 'application/x-www-form-urlencoded'; - form.acceptCharset = "UTF-8"; - form.appendChild(area); - _document.body.appendChild(form); - } - var form = that._send_form; - var area = that._send_area; - var id = 'a' + utils.random_string(8); - form.target = id; - form.action = url + '/jsonp_send?i=' + id; - - var iframe; - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - iframe = _document.createElement(' + -->