From b3ed2b515592f624d736dd01b76f828ec1d824f9 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 23 Nov 2015 14:31:45 +0530 Subject: [PATCH 01/25] Porting tern analysis of JS code to Node. --- .gitmodules | 3 + .../JavaScriptCodeHints/ScopeManager.js | 300 ++++--- .../node/TernNodeDomain.js | 796 ++++++++++++++++++ 3 files changed, 940 insertions(+), 159 deletions(-) create mode 100644 src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js diff --git a/.gitmodules b/.gitmodules index a38f27ab4c9..0318eabd8e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,9 @@ [submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/tern"] path = src/extensions/default/JavaScriptCodeHints/thirdparty/tern url = https://github.com/marijnh/tern.git +[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] + path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern + url = https://github.com/marijnh/tern.git [submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/acorn"] path = src/extensions/default/JavaScriptCodeHints/thirdparty/acorn url = https://github.com/marijnh/acorn.git diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 69160ef061a..4b491758aa0 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -29,7 +29,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, brackets, $, Worker, setTimeout */ +/*global define, brackets, $ */ define(function (require, exports, module) { "use strict"; @@ -48,7 +48,8 @@ define(function (require, exports, module) { PreferencesManager = brackets.getModule("preferences/PreferencesManager"), ProjectManager = brackets.getModule("project/ProjectManager"), Strings = brackets.getModule("strings"), - StringUtils = brackets.getModule("utils/StringUtils"); + StringUtils = brackets.getModule("utils/StringUtils"), + NodeDomain = brackets.getModule("utils/NodeDomain"); var HintUtils = require("HintUtils"), MessageIds = require("MessageIds"), @@ -60,11 +61,17 @@ define(function (require, exports, module) { builtinLibraryNames = [], isDocumentDirty = false, _hintCount = 0, - currentWorker = null, + currentModule = null, documentChanges = null, // bounds of document changes preferences = null, deferredPreferences = null; - + + var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), + _nodePath = "node/TernNodeDomain", + _domainPath = [_modulePath, _nodePath].join("/"); + + /* var _isNodeCacheReady = false;*/ + var MAX_HINTS = 30, // how often to reset the tern server LARGE_LINE_CHANGE = 100, LARGE_LINE_COUNT = 10000, @@ -85,7 +92,7 @@ define(function (require, exports, module) { * Read in the json files that have type information for the builtins, dom,etc */ function initTernEnv() { - var path = ExtensionUtils.getModulePath(module, "thirdparty/tern/defs/"), + var path = ExtensionUtils.getModulePath(module, "node/thirdparty/tern/defs/"), files = builtinFiles, library; @@ -174,12 +181,12 @@ define(function (require, exports, module) { } /** - * Send a message to the tern worker - if the worker is being initialized, + * Send a message to the tern module - if the module is being initialized, * the message will not be posted until initialization is complete */ function postMessage(msg) { - if (currentWorker) { - currentWorker.postMessage(msg); + if (currentModule) { + currentModule.postMessage(msg); } } @@ -261,7 +268,7 @@ define(function (require, exports, module) { } /** - * Add a pending request waiting for the tern-worker to complete. + * Add a pending request waiting for the tern-module to complete. * If file is a detected exclusion, then reject request. * * @param {string} file - the name of the file @@ -322,7 +329,7 @@ define(function (require, exports, module) { * @return {string} returns the path we resolved when we tried to parse the file, or undefined */ function getResolvedPath(file) { - return currentWorker.getResolvedPath(file); + return currentModule.getResolvedPath(file); } /** @@ -395,10 +402,10 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern web worker when + * Handle the response from the tern node when * it responds with the definition * - * @param response - the response from the worker + * @param response - the response from the node domain */ function handleJumptoDef(response) { @@ -635,11 +642,11 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern web worker when + * Handle the response from the tern node when * it responds with the list of completions * * @param {{file: string, offset: {line: number, ch: number}, completions:Array., - * properties:Array.}} response - the response from the worker + * properties:Array.}} response - the response from node domain */ function handleTernCompletions(response) { @@ -651,7 +658,7 @@ define(function (require, exports, module) { type = response.type, error = response.error, $deferredHints = getPendingRequest(file, offset, type); - + if ($deferredHints) { if (error) { $deferredHints.reject(); @@ -666,12 +673,12 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern web worker when + * Handle the response from the tern node when * it responds to the get guesses message. * * @param {{file: string, type: string, offset: {line: number, ch: number}, * properties: Array.}} response - - * the response from the worker contains the guesses for a + * the response from node domain contains the guesses for a * property lookup. */ function handleGetGuesses(response) { @@ -686,10 +693,10 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern web worker when + * Handle the response from the tern node when * it responds to the update file message. * - * @param {{path: string, type: string}} response - the response from the worker + * @param {{path: string, type: string}} response - the response from node domain */ function handleUpdateFile(response) { @@ -705,7 +712,7 @@ define(function (require, exports, module) { /** * Handle timed out inference * - * @param {{path: string, type: string}} response - the response from the worker + * @param {{path: string, type: string}} response - the response from node domain */ function handleTimedOut(response) { @@ -746,12 +753,12 @@ define(function (require, exports, module) { } /** - * Encapsulate all the logic to talk to the worker thread. This will create - * a new instance of a TernWorker, which the rest of the hinting code can use to talk - * to the worker, without worrying about initialization, priming the pump, etc. + * Encapsulate all the logic to talk to the tern module. This will create + * a new instance of a TernModule, which the rest of the hinting code can use to talk + * to the tern node domain, without worrying about initialization, priming the pump, etc. * */ - function TernWorker() { + function TernModule() { var ternPromise = null, addFilesPromise = null, rootTernDir = null, @@ -761,7 +768,7 @@ define(function (require, exports, module) { numInitialFiles = 0, numResolvedFiles = 0, numAddedFiles = 0, - _ternWorker = null; + _ternNodeDomain = null; /** * @param {string} file a relative path @@ -783,33 +790,33 @@ define(function (require, exports, module) { } /** - * Send a message to the tern worker - if the worker is being initialized, + * Send a message to the tern node - if the module is being initialized, * the message will not be posted until initialization is complete */ function postMessage(msg) { - addFilesPromise.done(function (ternWorker) { + addFilesPromise.done(function (ternModule) { // If an error came up during file handling, bail out now - if (!ternWorker) { + if (!_ternNodeDomain) { return; } if (config.debug) { console.debug("Sending message", msg); } - ternWorker.postMessage(msg); + _ternNodeDomain.exec("invokeTernCommand", msg); }); } /** - * Send a message to the tern worker - this is only for messages that + * Send a message to the tern node - this is only for messages that * need to be sent before and while the addFilesPromise is being resolved. */ function _postMessageByPass(msg) { - ternPromise.done(function (ternWorker) { + ternPromise.done(function (ternModule) { if (config.debug) { console.debug("Sending message", msg); } - ternWorker.postMessage(msg); + _ternNodeDomain.exec("invokeTernCommand", msg); }); } @@ -832,9 +839,9 @@ define(function (require, exports, module) { } /** - * Handle a request from the worker for text of a file + * Handle a request from the tern node for text of a file * - * @param {{file:string}} request - the request from the worker. Should be an Object containing the name + * @param {{file:string}} request - the request from the tern node. Should be an Object containing the name * of the file tern wants the contents of */ function handleTernGetFile(request) { @@ -851,7 +858,7 @@ define(function (require, exports, module) { /** * Helper function to get the text of a given document and send it to tern. - * If DocumentManager successfully gets the file's text then we'll send it to the tern worker. + * If DocumentManager successfully gets the file's text then we'll send it to the tern node. * The Promise for getDocumentText() is returned so that custom fail functions can be used. * * @param {string} filePath - the path of the file to get the text of @@ -937,10 +944,10 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern web worker when + * Handle the response from the tern node when * it responds to the prime pump message. * - * @param {{path: string, type: string}} response - the response from the worker + * @param {{path: string, type: string}} response - the response from node domain */ function handlePrimePumpCompletion(response) { @@ -972,7 +979,7 @@ define(function (require, exports, module) { } numAddedFiles += files.length; - ternPromise.done(function (worker) { + ternPromise.done(function (ternModule) { var msg = { type : MessageIds.TERN_ADD_FILES_MSG, files : files @@ -981,7 +988,7 @@ define(function (require, exports, module) { if (config.debug) { console.debug("Sending message", msg); } - worker.postMessage(msg); + _ternNodeDomain.exec("invokeTernCommand", msg); }); } else { @@ -1027,70 +1034,72 @@ define(function (require, exports, module) { } /** - * Init the web worker that does all the code hinting work. - * - * If a worker already exists, then this will terminate that worker and - * start a new worker - this helps alleviate leaks that may be ocurring in - * the code that the worker runs. + * Init the Tern module that does all the code hinting work. */ - function initTernWorker() { - if (_ternWorker) { - _ternWorker.terminate(); - } - var workerDeferred = $.Deferred(); - ternPromise = workerDeferred.promise(); - var path = ExtensionUtils.getModulePath(module, "tern-worker.js"); - _ternWorker = new Worker(path); - - _ternWorker.addEventListener("message", function (e) { - if (config.debug) { - console.debug("Message received", e); - } - - var response = e.data, - type = response.type; - - if (type === MessageIds.TERN_COMPLETIONS_MSG || - type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { - // handle any completions the worker calculated - handleTernCompletions(response); - } else if (type === MessageIds.TERN_GET_FILE_MSG) { - // handle a request for the contents of a file - handleTernGetFile(response); - } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { - handleJumptoDef(response); - } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { - handlePrimePumpCompletion(response); - } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { - handleGetGuesses(response); - } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { - handleUpdateFile(response); - } else if (type === MessageIds.TERN_INFERENCE_TIMEDOUT) { - handleTimedOut(response); - } else if (type === MessageIds.TERN_WORKER_READY) { - workerDeferred.resolveWith(null, [_ternWorker]); - } else { - console.log("Worker: " + (response.log || response)); - } - }); + function initTernModule() { + var moduleDeferred = $.Deferred(); + ternPromise = moduleDeferred.promise(); + if (_ternNodeDomain) { + _ternNodeDomain.exec("resetTernServer"); + moduleDeferred.resolveWith(null, [_ternNodeDomain]); + } else { + _ternNodeDomain = new NodeDomain("TernNodeDomain", _domainPath); + _ternNodeDomain.on("data", function (evt, data) { + if (config.debug) { + console.log("Message received", data.type); + } + + var response = data, + type = response.type; + + if (type === MessageIds.TERN_COMPLETIONS_MSG || + type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { + // handle any completions the tern server calculated + handleTernCompletions(response); + } else if (type === MessageIds.TERN_GET_FILE_MSG) { + // handle a request for the contents of a file + handleTernGetFile(response); + } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { + handleJumptoDef(response); + } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { + handlePrimePumpCompletion(response); + } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { + handleGetGuesses(response); + } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { + handleUpdateFile(response); + } else if (type === MessageIds.TERN_INFERENCE_TIMEDOUT) { + handleTimedOut(response); + } else { + console.log("Tern Module: " + (response.log || response)); + } + }); - // Set the initial configuration for the worker - _ternWorker.postMessage({ - type: MessageIds.SET_CONFIG, - config: config - }); + _ternNodeDomain.promise().done(function () { + + _ternNodeDomain.exec("setInterface", { + messageIds : MessageIds + }); + + _ternNodeDomain.exec("invokeTernCommand", { + type: MessageIds.SET_CONFIG, + config: config + }); + moduleDeferred.resolveWith(null, [_ternNodeDomain]); + }); + } } + /** * Create a new tern server. */ function initTernServer(dir, files) { - initTernWorker(); + initTernModule(); numResolvedFiles = 0; numAddedFiles = 0; stopAddingFiles = false; numInitialFiles = files.length; - ternPromise.done(function (worker) { + ternPromise.done(function (ternModule) { var msg = { type : MessageIds.TERN_INIT_MSG, dir : dir, @@ -1098,17 +1107,7 @@ define(function (require, exports, module) { env : ternEnvironment, timeout : PreferencesManager.get("jscodehints.inferenceTimeout") }; - - if (worker) { - if (config.debug) { - console.debug("Sending message", msg); - } - worker.postMessage(msg); - } else { - if (config.debug) { - console.debug("Worker null. Cannot send message", msg); - } - } + _ternNodeDomain.exec("invokeTernCommand", msg); }); rootTernDir = dir + "/"; } @@ -1153,10 +1152,10 @@ define(function (require, exports, module) { var updateFilePromise = updateTernFile(previousDocument); updateFilePromise.done(function () { primePump(path); - addFilesDeferred.resolveWith(null, [_ternWorker]); + addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); } else { - addFilesDeferred.resolveWith(null, [_ternWorker]); + addFilesDeferred.resolveWith(null, [_ternNodeDomain]); } isDocumentDirty = false; @@ -1210,14 +1209,14 @@ define(function (require, exports, module) { // for completion. primePump(path); - addFilesDeferred.resolveWith(null, [_ternWorker]); + addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); } else { - addFilesDeferred.resolveWith(null, [_ternWorker]); + addFilesDeferred.resolveWith(null, [_ternNodeDomain]); } }); } else { - addFilesDeferred.resolveWith(null, [_ternWorker]); + addFilesDeferred.resolveWith(null, [_ternNodeDomain]); } }); }); @@ -1245,33 +1244,23 @@ define(function (require, exports, module) { /** * Do some cleanup when a project is closed. * - * We can clean up the web worker we use to calculate hints now, since + * We can clean up the node tern server we use to calculate hints now, since * we know we will need to re-init it in any new project that is opened. */ - function closeWorker() { - function terminateWorker() { - var worker = _ternWorker; - - // Worker can be null if an error condition came up previously - if (!worker) { - return; + function resetModule() { + function resetTernServer() { + if (_ternNodeDomain.ready()) { + _ternNodeDomain.exec('resetTernServer'); } - setTimeout(function () { - // give pending requests a chance to finish - worker.terminate(); - worker = null; - }, 1000); - _ternWorker = null; - resolvedFiles = {}; } - if (_ternWorker) { + if (_ternNodeDomain) { if (addFilesPromise) { - // If we're in the middle of added files, don't terminate - // until we're done or we might get NPEs - addFilesPromise.done(terminateWorker).fail(terminateWorker); + // If we're in the middle of added files, don't reset + // until we're done + addFilesPromise.done(resetTernServer).fail(resetTernServer); } else { - terminateWorker(); + resetTernServer(); } } } @@ -1280,7 +1269,7 @@ define(function (require, exports, module) { addFilesPromise.done(func); } - this.closeWorker = closeWorker; + this.resetModule = resetModule; this.handleEditorChange = handleEditorChange; this.postMessage = postMessage; this.getResolvedPath = getResolvedPath; @@ -1292,55 +1281,50 @@ define(function (require, exports, module) { var resettingDeferred = null; /** - * reset the tern worker thread, if necessary. - * - * To avoid memory leaks in the worker thread we periodically kill - * the web worker instance, and start a new one. To avoid a performance - * hit when we do this we start up a new worker, and don't kill the old - * one unitl the new one is initialized. + * reset the tern module, if necessary. * * During debugging, you can turn this automatic resetting behavior off * by running this in the console: * brackets._configureJSCodeHints({ noReset: true }) * * This function is also used in unit testing with the "force" flag to - * reset the worker for each test to start with a clean environment. + * reset the module for each test to start with a clean environment. * * @param {Session} session * @param {Document} document * @param {boolean} force true to force a reset regardless of how long since the last one - * @return {Promise} Promise resolved when the worker is ready. - * The new (or current, if there was no reset) worker is passed to the callback. + * @return {Promise} Promise resolved when the module is ready. + * The new (or current, if there was no reset) module is passed to the callback. */ function _maybeReset(session, document, force) { - var newWorker; + var newTernModule; // if we're in the middle of a reset, don't have to check - // the new worker will be online soon + // the new module will be online soon if (!resettingDeferred) { // We don't reset if the debugging flag is set - // because it's easier to debug if the worker isn't - // getting shut down all the time. + // because it's easier to debug if the module isn't + // getting reset all the time. if (force || (!config.noReset && ++_hintCount > MAX_HINTS)) { if (config.debug) { - console.debug("Resetting tern worker"); + console.debug("Resetting tern module"); } resettingDeferred = new $.Deferred(); - newWorker = new TernWorker(); - newWorker.handleEditorChange(session, document, null); - newWorker.whenReady(function () { - // tell the old worker to shut down - currentWorker.closeWorker(); - currentWorker = newWorker; - resettingDeferred.resolve(currentWorker); + newTernModule = new TernModule(); + newTernModule.handleEditorChange(session, document, null); + newTernModule.whenReady(function () { + // reset the old module + currentModule.resetModule(); + currentModule = newTernModule; + resettingDeferred.resolve(currentModule); // all done reseting resettingDeferred = null; }); _hintCount = 0; } else { var d = new $.Deferred(); - d.resolve(currentWorker); + d.resolve(currentModule); return d.promise(); } } @@ -1472,22 +1456,20 @@ define(function (require, exports, module) { */ function handleEditorChange(session, document, previousDocument) { - if (!currentWorker) { - currentWorker = new TernWorker(); + if (!currentModule) { + currentModule = new TernModule(); } - return currentWorker.handleEditorChange(session, document, previousDocument); + return currentModule.handleEditorChange(session, document, previousDocument); } /** * Do some cleanup when a project is closed. * - * We can clean up the web worker we use to calculate hints now, since - * we know we will need to re-init it in any new project that is opened. + * Clean up previous analysis data from the module */ function handleProjectClose() { - if (currentWorker) { - currentWorker.closeWorker(); - currentWorker = null; + if (currentModule) { + currentModule.resetModule(); } } @@ -1510,7 +1492,7 @@ define(function (require, exports, module) { /** * @private * - * Update the configuration in the worker. + * Update the configuration in the tern node. */ function _setConfig(configUpdate) { config = brackets._configureJSCodeHints.config; diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js new file mode 100644 index 00000000000..5e58b391bbd --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -0,0 +1,796 @@ +/* + * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + * + * 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. + * + */ + +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, +maxerr: 50, node: true */ + +var config = {}; + +(function () { + "use strict"; + + var _domainManager; + var MessageIds; + var Tern, Infer; + var self = { + postMessage: function (data) { + _domainManager.emitEvent("TernNodeDomain", "data", [data]); + } + }; + + //MessageIds /*= require(basepath.join(path.sep) + "/MessageIds")*/; + + Tern = require("./node_modules/tern/lib/tern"); + Infer = require("./node_modules/tern/lib/infer"); + + var ternServer = null, + inferenceTimeout; + + // Save the tern callbacks for when we get the contents of the file + var fileCallBacks = {}; + + /** + * Provide the contents of the requested file to tern + * @param {string} name - the name of the file + * @param {Function} next - the function to call with the text of the file + * once it has been read in. + */ + function getFile(name, next) { + // save the callback + fileCallBacks[name] = next; + // post a message back to the main thread to get the file contents + self.postMessage({ + type: MessageIds.TERN_GET_FILE_MSG, + file: name + }); + } + + /** + * Send a log message back from the node to the main thread + * @private + * @param {string} msg - the log message + */ + function _log(msg) { + console.log(msg); + } + + /** + * Report exception + * @private + * @param {Error} e - the error object + */ + function _reportError(e, file) { + if (e instanceof Infer.TimedOut) { + // Post a message back to the main thread with timedout info + self.postMessage({ + type: MessageIds.TERN_INFERENCE_TIMEDOUT, + file: file + }); + } else { + _log("Error thrown in tern_node domain:" + e.message + "\n" + e.stack); + } + } + + /** + * Handle a response from the main thread providing the contents of a file + * @param {string} file - the name of the file + * @param {string} text - the contents of the file + */ + function handleGetFile(file, text) { + var next = fileCallBacks[file]; + if (next) { + try { + next(null, text); + } catch (e) { + _reportError(e, file); + } + } + delete fileCallBacks[file]; + } + + /** + * Create a new tern server. + * + * @param {Object} env - an Object with the environment, as read in from + * the json files in thirdparty/tern/defs + * @param {Array.} files - a list of filenames tern should be aware of + */ + function initTernServer(env, files) { + var ternOptions = { + defs: env, + async: true, + getFile: getFile, + plugins: {requirejs: {}, doc_comment: true, angular: true} + }; + + // If a server is already created just reset the analysis data + if (ternServer) { + ternServer.reset(); + } else { + ternServer = new Tern.Server(ternOptions); + } + + files.forEach(function (file) { + ternServer.addFile(file); + }); + + } + + /** + * Resets an existing tern server. + */ + function resetTernServer() { + // If a server is already created just reset the analysis data + if (ternServer) { + ternServer.reset(); + } + } + + /** + * Create a "empty" update object. + * + * @param {string} path - full path of the file. + * @return {{type: string, name: string, offsetLines: number, text: string}} - + * "empty" update. + + */ + function createEmptyUpdate(path) { + return {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, + name: path, + offsetLines: 0, + text: ""}; + } + + /** + * Build an object that can be used as a request to tern. + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {string} query - the type of request being made + * @param {{line: number, ch: number}} offset - + */ + function buildRequest(fileInfo, query, offset) { + query = {type: query}; + query.start = offset; + query.end = offset; + query.file = (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) ? "#0" : fileInfo.name; + query.filter = false; + query.sort = false; + query.depths = true; + query.guess = true; + query.origins = true; + query.types = true; + query.expandWordForward = false; + query.lineCharPositions = true; + + var request = {query: query, files: [], offset: offset, timeout: inferenceTimeout}; + if (fileInfo.type !== MessageIds.TERN_FILE_INFO_TYPE_EMPTY) { + request.files.push(fileInfo); + } + + return request; + } + + /** + * Get definition location + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - the offset into the + * file for cursor + */ + function getJumptoDef(fileInfo, offset) { + var request = buildRequest(fileInfo, "definition", offset); + // request.query.typeOnly = true; // FIXME: tern doesn't work exactly right yet. + + try { + ternServer.request(request, function (error, data) { + if (error) { + _log("Error returned from Tern 'definition' request: " + error); + self.postMessage({type: MessageIds.TERN_JUMPTODEF_MSG, file: fileInfo.name, offset: offset}); + return; + } + var response = {type: MessageIds.TERN_JUMPTODEF_MSG, + file: fileInfo.name, + resultFile: data.file, + offset: offset, + start: data.start, + end: data.end + }; + + request = buildRequest(fileInfo, "type", offset); + // See if we can tell if the reference is to a Function type + ternServer.request(request, function (error, data) { + if (!error) { + response.isFunction = data.type.length > 2 && data.type.substring(0, 2) === "fn"; + } + + // Post a message back to the main thread with the definition + self.postMessage(response); + }); + + }); + } catch (e) { + _reportError(e, fileInfo.name); + } + } + + /** + * Get all the known properties for guessing. + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - + * the offset into the file where we want completions for + * @param {string} type - the type of the message to reply with. + */ + function getTernProperties(fileInfo, offset, type) { + + var request = buildRequest(fileInfo, "properties", offset), + i; + //_log("tern properties: request " + request.type + dir + " " + file); + try { + ternServer.request(request, function (error, data) { + var properties = []; + if (error) { + _log("Error returned from Tern 'properties' request: " + error); + } else { + //_log("tern properties: completions = " + data.completions.length); + for (i = 0; i < data.completions.length; ++i) { + var property = data.completions[i]; + properties.push({value: property, type: property.type, guess: true}); + } + } + + // Post a message back to the main thread with the completions + self.postMessage({type: type, + file: fileInfo.name, + offset: offset, + properties: properties + }); + }); + } catch (e) { + _reportError(e, fileInfo.name); + } + } + + /** + * Get the completions for the given offset + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - + * the offset into the file where we want completions for + * @param {boolean} isProperty - true if getting a property hint, + * otherwise getting an identifier hint. + */ + function getTernHints(fileInfo, offset, isProperty) { + var request = buildRequest(fileInfo, "completions", offset), + i; + //_log("request " + dir + " " + file + " " + offset /*+ " " + text */); + try { + ternServer.request(request, function (error, data) { + var completions = []; + if (error) { + _log("Error returned from Tern 'completions' request: " + error); + } else { + //_log("found " + data.completions.length + " for " + file + "@" + offset); + for (i = 0; i < data.completions.length; ++i) { + var completion = data.completions[i]; + completions.push({value: completion.name, type: completion.type, depth: completion.depth, + guess: completion.guess, origin: completion.origin}); + } + } + + if (completions.length > 0 || !isProperty) { + // Post a message back to the main thread with the completions + self.postMessage({type: MessageIds.TERN_COMPLETIONS_MSG, + file: fileInfo.name, + offset: offset, + completions: completions + }); + } else { + // if there are no completions, then get all the properties + getTernProperties(fileInfo, offset, MessageIds.TERN_COMPLETIONS_MSG); + } + }); + } catch (e) { + _reportError(e, fileInfo.name); + } + } + + /** + * Given a Tern type object, convert it to an array of Objects, where each object describes + * a parameter. + * + * @param {!Infer.Fn} inferFnType - type to convert. + * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. + */ + function getParameters(inferFnType) { + + // work around define functions before use warning. + var recordTypeToString, inferTypeToString, processInferFnTypeParameters, inferFnTypeToString; + + /** + * Convert an infer array type to a string. + * + * Formatted using google closure style. For example: + * + * "Array." + * + * @param {Infer.Arr} inferArrType + * + * @return {string} - array formatted in google closure style. + * + */ + function inferArrTypeToString(inferArrType) { + var result = "Array.<"; + + inferArrType.props[""].types.forEach(function (value, i) { + if (i > 0) { + result += ", "; + } + result += inferTypeToString(value); + }); + + // workaround case where types is zero length + if (inferArrType.props[""].types.length === 0) { + result += "Object"; + } + result += ">"; + + return result; + } + + /** + * Convert properties to a record type annotation. + * + * @param {Object} props + * @return {string} - record type annotation + */ + recordTypeToString = function (props) { + var result = "{", + first = true, + prop; + + for (prop in props) { + if (Object.prototype.hasOwnProperty.call(props, prop)) { + if (!first) { + result += ", "; + } + + first = false; + result += prop + ": " + inferTypeToString(props[prop]); + } + } + + result += "}"; + + return result; + }; + + /** + * Convert an infer type to a string. + * + * @param {*} inferType - one of the Infer's types; Infer.Prim, Infer.Arr, Infer.ANull. Infer.Fn functions are + * not handled here. + * + * @return {string} + * + */ + inferTypeToString = function (inferType) { + var result; + + if (inferType instanceof Infer.AVal) { + inferType = inferType.types[0]; + } + + if (inferType instanceof Infer.Prim) { + result = inferType.toString(); + if (result === "string") { + result = "String"; + } else if (result === "number") { + result = "Number"; + } else if (result === "boolean") { + result = "Boolean"; + } + } else if (inferType instanceof Infer.Arr) { + result = inferArrTypeToString(inferType); + } else if (inferType instanceof Infer.Fn) { + result = inferFnTypeToString(inferType); + } else if (inferType instanceof Infer.Obj) { + if (inferType.name === undefined) { + result = recordTypeToString(inferType.props); + } else { + result = inferType.name; + } + } else { + result = "Object"; + } + + return result; + }; + + /** + * Format the given parameter array. Handles separators between + * parameters, syntax for optional parameters, and the order of the + * parameter type and parameter name. + * + * @param {!Array.<{name: string, type: string, isOptional: boolean}>} params - + * array of parameter descriptors + * @param {function(string)=} appendSeparators - callback function to append separators. + * The separator is passed to the callback. + * @param {function(string, number)=} appendParameter - callback function to append parameter. + * The formatted parameter type and name is passed to the callback along with the + * current index of the parameter. + * @param {boolean=} typesOnly - only show parameter types. The + * default behavior is to include both parameter names and types. + * @return {string} - formatted parameter hint + */ + function formatParameterHint(params, appendSeparators, appendParameter, typesOnly) { + var result = "", + pendingOptional = false; + + params.forEach(function (value, i) { + var param = value.type, + separators = ""; + + if (value.isOptional) { + // if an optional param is following by an optional parameter, then + // terminate the bracket. Otherwise enclose a required parameter + // in the same bracket. + if (pendingOptional) { + separators += "]"; + } + + pendingOptional = true; + } + + if (i > 0) { + separators += ", "; + } + + if (value.isOptional) { + separators += "["; + } + + if (appendSeparators) { + appendSeparators(separators); + } + + result += separators; + + if (!typesOnly) { + param += " " + value.name; + } + + if (appendParameter) { + appendParameter(param, i); + } + + result += param; + + }); + + if (pendingOptional) { + if (appendSeparators) { + appendSeparators("]"); + } + + result += "]"; + } + + return result; + } + + /** + * Convert an infer function type to a Google closure type string. + * + * @param {Infer.Fn} inferType - type to convert. + * @return {string} - function type as a string. + */ + inferFnTypeToString = function (inferType) { + var result = "function(", + params = processInferFnTypeParameters(inferType); + + result += /*HintUtils2.*/formatParameterHint(params, null, null, true); + if (inferType.retval) { + result += "):"; + result += inferTypeToString(inferType.retval); + } + + return result; + }; + + /** + * Convert an infer function type to string. + * + * @param {*} inferType - one of the Infer's types; Infer.Fn, Infer.Prim, Infer.Arr, Infer.ANull + * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. + */ + processInferFnTypeParameters = function (inferType) { + var params = [], + i; + + for (i = 0; i < inferType.args.length; i++) { + var param = {}, + name = inferType.argNames[i], + type = inferType.args[i]; + + if (!name) { + name = "param" + (i + 1); + } + + if (name[name.length - 1] === "?") { + name = name.substring(0, name.length - 1); + param.isOptional = true; + } + + param.name = name; + param.type = inferTypeToString(type); + params.push(param); + } + + return params; + }; + + return processInferFnTypeParameters(inferFnType); + } + + /** + * Get the function type for the given offset + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - + * the offset into the file where we want completions for + */ + function handleFunctionType(fileInfo, offset) { + var request = buildRequest(fileInfo, "type", offset), + error; + + request.query.preferFunction = true; + + var fnType = ""; + try { + ternServer.request(request, function (ternError, data) { + + if (ternError) { + _log("Error for Tern request: \n" + JSON.stringify(request) + "\n" + ternError); + error = ternError.toString(); + } else { + var file = ternServer.findFile(fileInfo.name); + + // convert query from partial to full offsets + var newOffset = offset; + if (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) { + newOffset = {line: offset.line + fileInfo.offsetLines, ch: offset.ch}; + } + + request = buildRequest(createEmptyUpdate(fileInfo.name), "type", newOffset); + + var expr = Tern.findQueryExpr(file, request.query); + Infer.resetGuessing(); + var type = Infer.expressionType(expr); + type = type.getFunctionType() || type.getType(); + + if (type) { + fnType = getParameters(type); + } else { + ternError = "No parameter type found"; + _log(ternError); + } + } + }); + } catch (e) { + _reportError(e, fileInfo.name); + } + + // Post a message back to the main thread with the completions + self.postMessage({type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG, + file: fileInfo.name, + offset: offset, + fnType: fnType, + error: error + }); + } + + /** + * Add an array of files to tern. + * + * @param {Array.} files - each string in the array is the full + * path of a file. + */ + function handleAddFiles(files) { + files.forEach(function (file) { + ternServer.addFile(file); + }); + } + + /** + * Update the context of a file in tern. + * + * @param {string} path - full path of file. + * @param {string} text - content of the file. + */ + function handleUpdateFile(path, text) { + + ternServer.addFile(path, text); + + self.postMessage({type: MessageIds.TERN_UPDATE_FILE_MSG, + path: path + }); + + // reset to get the best hints with the updated file. + ternServer.reset(); + } + + /** + * Make a completions request to tern to force tern to resolve files + * and create a fast first lookup for the user. + * @param {string} path - the path of the file + */ + function handlePrimePump(path) { + var fileInfo = createEmptyUpdate(path), + request = buildRequest(fileInfo, "completions", {line: 0, ch: 0}); + + try { + ternServer.request(request, function (error, data) { + // Post a message back to the main thread + self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG, + path: path + }); + }); + } catch (e) { + _reportError(e, path); + } + } + + /** + * Updates the configuration, typically for debugging purposes. + * + * @param {Object} configUpdate new configuration + */ + function setConfig(configUpdate) { + config = configUpdate; + } + + function invokeTernCommand(commandConfig) { + var file, text, offset, + request = commandConfig, + type = request.type; + if (config.debug) { + _log("Message received " + type); + } + + if (type === MessageIds.TERN_INIT_MSG) { + var env = request.env, + files = request.files; + inferenceTimeout = request.timeout; + initTernServer(env, files); + } else if (type === MessageIds.TERN_COMPLETIONS_MSG) { + offset = request.offset; + getTernHints(request.fileInfo, offset, request.isProperty); + } else if (type === MessageIds.TERN_GET_FILE_MSG) { + file = request.file; + text = request.text; + handleGetFile(file, text); + } else if (type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { + offset = request.offset; + handleFunctionType(request.fileInfo, offset); + } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { + offset = request.offset; + getJumptoDef(request.fileInfo, offset); + } else if (type === MessageIds.TERN_ADD_FILES_MSG) { + handleAddFiles(request.files); + } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { + handlePrimePump(request.path); + } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { + offset = request.offset; + getTernProperties(request.fileInfo, offset, MessageIds.TERN_GET_GUESSES_MSG); + } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { + handleUpdateFile(request.path, request.text); + } else if (type === MessageIds.SET_CONFIG) { + setConfig(request.config); + } else { + _log("Unknown message: " + JSON.stringify(request)); + } + } + + function setInterface(msgInterface) { + MessageIds = msgInterface.messageIds; + } + + /** + * Initialize the test domain with commands and events related to find in files. + * @param {DomainManager} domainManager The DomainManager for the TernNodeDomain + */ + function init(domainManager) { + if (!domainManager.hasDomain("TernNodeDomain")) { + domainManager.registerDomain("TernNodeDomain", {major: 0, minor: 1}); + } + + _domainManager = domainManager; + + domainManager.registerCommand( + "TernNodeDomain", // domain name + "invokeTernCommand", // command name + invokeTernCommand, // command handler function + false, // this command is synchronous in Node + "Invokes a tern command on node", + [{name: "commandConfig", // parameters + type: "object", + description: "Object containing tern command configuration"}] + ); + + domainManager.registerCommand( + "TernNodeDomain", // domain name + "setInterface", // command name + setInterface, // command handler function + false, // this command is synchronous in Node + "Sets the shared message interface", + [{name: "msgInterface", // parameters + type: "object", + description: "Object containing messageId enums"}] + ); + + domainManager.registerCommand( + "TernNodeDomain", // domain name + "resetTernServer", // command name + resetTernServer, // command handler function + true, // this command is synchronous in Node + "Resets an existing tern server" + ); + + domainManager.registerEvent( + "TernNodeDomain", // domain name + "data", // event name + [ + { + name: "data", + type: "Object", + description: "data to be returned to main thread" + } + ] + ); + + console.log("Tern Node ready!"); + } + + exports.init = init; + +}()); From c8a7df35cafefa59f0fa36c0ef6daff805cfd361 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Tue, 24 Nov 2015 14:50:33 +0530 Subject: [PATCH 02/25] Removing old tern submodule config --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0318eabd8e6..7e02edca8d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "src/thirdparty/mustache"] path = src/thirdparty/mustache url = https://github.com/janl/mustache.js.git -[submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/tern"] - path = src/extensions/default/JavaScriptCodeHints/thirdparty/tern - url = https://github.com/marijnh/tern.git [submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern url = https://github.com/marijnh/tern.git From 76bbb481353d3ebdd458ded704f4c05c892ee31e Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 26 Nov 2015 17:25:01 +0530 Subject: [PATCH 03/25] JS text cache in HTML Mixed Mode. gitmodule mapping changes. --- .gitmodules | 4 ++-- .../default/JavaScriptCodeHints/ScopeManager.js | 13 ++++++++++++- .../JavaScriptCodeHints/node/TernNodeDomain.js | 14 ++++++-------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.gitmodules b/.gitmodules index 7e02edca8d6..9f61d2a4bdf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,8 +7,8 @@ [submodule "src/thirdparty/mustache"] path = src/thirdparty/mustache url = https://github.com/janl/mustache.js.git -[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] - path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern +[submodule "src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern"] + path = src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern url = https://github.com/marijnh/tern.git [submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/acorn"] path = src/extensions/default/JavaScriptCodeHints/thirdparty/acorn diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 4b491758aa0..8fc19d0b8ba 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -79,6 +79,12 @@ define(function (require, exports, module) { var config = {}; + /** + * Used to cache JS Code for hinting in HTML Mixed mode + */ + var cachedJStext = ""; + + /** * An array of library names that contain JavaScript builtins definitions. * @@ -561,7 +567,7 @@ define(function (require, exports, module) { if (isHtmlFile) { result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, - text: session.getJavascriptText()}; + text: cachedJStext}; } else if (!documentChanges) { result = {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, name: path, @@ -1459,6 +1465,11 @@ define(function (require, exports, module) { if (!currentModule) { currentModule = new TernModule(); } + + if (LanguageManager.getLanguageForPath(document.file.fullPath).getId() === "html") { + cachedJStext = session.getJavascriptText(); + } + return currentModule.handleEditorChange(session, document, previousDocument); } diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 5e58b391bbd..b5227901a67 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -21,8 +21,7 @@ * */ -/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, -maxerr: 50, node: true */ +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, node: true, regexp: true */ var config = {}; @@ -31,17 +30,14 @@ var config = {}; var _domainManager; var MessageIds; - var Tern, Infer; var self = { postMessage: function (data) { _domainManager.emitEvent("TernNodeDomain", "data", [data]); } }; - //MessageIds /*= require(basepath.join(path.sep) + "/MessageIds")*/; - - Tern = require("./node_modules/tern/lib/tern"); - Infer = require("./node_modules/tern/lib/infer"); + var Tern = require("./thirdparty/tern/lib/tern"), + Infer = require("./thirdparty/tern/lib/infer"); var ternServer = null, inferenceTimeout; @@ -185,6 +181,8 @@ var config = {}; query.types = true; query.expandWordForward = false; query.lineCharPositions = true; + query.docs = true; + query.urls = true; var request = {query: query, files: [], offset: offset, timeout: inferenceTimeout}; if (fileInfo.type !== MessageIds.TERN_FILE_INFO_TYPE_EMPTY) { @@ -309,7 +307,7 @@ var config = {}; for (i = 0; i < data.completions.length; ++i) { var completion = data.completions[i]; completions.push({value: completion.name, type: completion.type, depth: completion.depth, - guess: completion.guess, origin: completion.origin}); + guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}); } } From 39088dfe545120cf1c4ec2d853a15cb3e8ea8de0 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 26 Nov 2015 18:26:08 +0530 Subject: [PATCH 04/25] Submodules fix --- .../default/JavaScriptCodeHints/{ => node}/thirdparty/tern | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/extensions/default/JavaScriptCodeHints/{ => node}/thirdparty/tern (100%) diff --git a/src/extensions/default/JavaScriptCodeHints/thirdparty/tern b/src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern similarity index 100% rename from src/extensions/default/JavaScriptCodeHints/thirdparty/tern rename to src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern From d35be04d3d99ec190b0282216ca5b1601cbbc24d Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 26 Nov 2015 20:09:53 +0530 Subject: [PATCH 05/25] Fixing more submodule issues --- .gitmodules | 9 ++++++--- .../default/JavaScriptCodeHints/node/TernNodeDomain.js | 4 ++-- .../default/JavaScriptCodeHints/node/node_modules/acorn | 1 + .../default/JavaScriptCodeHints/node/node_modules/tern | 1 + .../default/JavaScriptCodeHints/node/thirdparty/tern | 1 - 5 files changed, 10 insertions(+), 6 deletions(-) create mode 160000 src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn create mode 160000 src/extensions/default/JavaScriptCodeHints/node/node_modules/tern delete mode 160000 src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern diff --git a/.gitmodules b/.gitmodules index 9f61d2a4bdf..c1fcf2b2faa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "src/thirdparty/mustache"] path = src/thirdparty/mustache url = https://github.com/janl/mustache.js.git -[submodule "src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern"] - path = src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern - url = https://github.com/marijnh/tern.git [submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/acorn"] path = src/extensions/default/JavaScriptCodeHints/thirdparty/acorn url = https://github.com/marijnh/acorn.git @@ -25,3 +22,9 @@ [submodule "src/extensions/default/JSLint/thirdparty/jslint"] path = src/extensions/default/JSLint/thirdparty/jslint url = https://github.com/peterflynn/JSLint.git +[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn"] + path = src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn + url = https://github.com/marijnh/acorn.git +[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] + path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern + url = https://github.com/marijnh/acorn.git diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index b5227901a67..3c82d55f9f9 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -36,8 +36,8 @@ var config = {}; } }; - var Tern = require("./thirdparty/tern/lib/tern"), - Infer = require("./thirdparty/tern/lib/infer"); + var Tern = require("./node_modules/tern/lib/tern"), + Infer = require("./node_modules/tern/lib/infer"); var ternServer = null, inferenceTimeout; diff --git a/src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn b/src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn new file mode 160000 index 00000000000..81483879d57 --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn @@ -0,0 +1 @@ +Subproject commit 81483879d57cac77a236a59bf4cc6eb29a94a145 diff --git a/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern b/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern new file mode 160000 index 00000000000..81483879d57 --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern @@ -0,0 +1 @@ +Subproject commit 81483879d57cac77a236a59bf4cc6eb29a94a145 diff --git a/src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern b/src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern deleted file mode 160000 index 7606a6448a8..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/node/thirdparty/tern +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7606a6448a8f7a2aacd50d10d9752440689803e8 From 64f4d7566d227acc5411be97dea38d291eba8064 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 26 Nov 2015 23:56:52 +0530 Subject: [PATCH 06/25] Tern default defs path fix --- src/extensions/default/JavaScriptCodeHints/ScopeManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 8fc19d0b8ba..a9301d2fd26 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -98,7 +98,7 @@ define(function (require, exports, module) { * Read in the json files that have type information for the builtins, dom,etc */ function initTernEnv() { - var path = ExtensionUtils.getModulePath(module, "node/thirdparty/tern/defs/"), + var path = ExtensionUtils.getModulePath(module, "node/node_modules/tern/defs/"), files = builtinFiles, library; From b9d73c10d5984608d8f931bb9fe9c9c970875136 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 7 Dec 2015 14:27:46 +0530 Subject: [PATCH 07/25] First draft for content extraction in node [UNIT TESTING IN PROGRESS] --- .../default/JavaScriptCodeHints/MessageIds.js | 6 +- .../JavaScriptCodeHints/ScopeManager.js | 17 +++++ .../node/ExtractFileContent.js | 72 +++++++++++++++++++ .../node/TernNodeDomain.js | 49 ++++++++----- 4 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js diff --git a/src/extensions/default/JavaScriptCodeHints/MessageIds.js b/src/extensions/default/JavaScriptCodeHints/MessageIds.js index 5ea34552e14..aa98d541599 100644 --- a/src/extensions/default/JavaScriptCodeHints/MessageIds.js +++ b/src/extensions/default/JavaScriptCodeHints/MessageIds.js @@ -38,7 +38,9 @@ define(function (require, exports, module) { TERN_GET_GUESSES_MSG = "GetGuesses", TERN_WORKER_READY = "WorkerReady", TERN_INFERENCE_TIMEDOUT = "InferenceTimedOut", - SET_CONFIG = "SetConfig"; + SET_CONFIG = "SetConfig", + TERN_UPDATE_DIRTY_FILE = "UpdateDirtyFileEntry", + TERN_CLEAR_DIRTY_FILES_LIST = "ClearDirtyFilesList"; // Message parameter constants var TERN_FILE_INFO_TYPE_PART = "part", @@ -61,6 +63,8 @@ define(function (require, exports, module) { exports.TERN_FILE_INFO_TYPE_EMPTY = TERN_FILE_INFO_TYPE_EMPTY; exports.TERN_INFERENCE_TIMEDOUT = TERN_INFERENCE_TIMEDOUT; exports.SET_CONFIG = SET_CONFIG; + exports.TERN_UPDATE_DIRTY_FILE = TERN_UPDATE_DIRTY_FILE; + exports.TERN_CLEAR_DIRTY_FILES_LIST = TERN_CLEAR_DIRTY_FILES_LIST; }); diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index a9301d2fd26..88f3e75afb0 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -757,6 +757,23 @@ define(function (require, exports, module) { ] ); } + + DocumentManager.on("dirtyFlagChange", function (event, changedDoc) { + if (changedDoc.file.fullPath) { + postMessage({ + type: MessageIds.TERN_UPDATE_DIRTY_FILE, + name: changedDoc.file.fullPath, + action: changedDoc.isDirty + }); + } + }); + + // Clear dirty document list in tern node domain + ProjectManager.on("beforeProjectClose", function () { + postMessage({ + type: MessageIds.TERN_CLEAR_DIRTY_FILES_LIST + }); + }); /** * Encapsulate all the logic to talk to the tern module. This will create diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js new file mode 100644 index 00000000000..799ae04f7ae --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + * + * 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. + * + */ + +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, node: true, regexp: true */ + +(function () { + "use strict"; + + var fs = require("fs"), + _dirtyFilesCache = []; + + function _clearDirtyFilesCache() { + _dirtyFilesCache = []; + } + + function _updateDirtyFilesCache(name, action) { + if (action) { + _dirtyFilesCache.push(name); + } else { + var index = _dirtyFilesCache.indexOf(name); + if (index >= 0) { + _dirtyFilesCache.splice(index, 1); + } + } + } + + function _readFile(fileName, callback) { + fs.readFile(fileName, 'utf8', function (err, data) { + var content = ""; + if (!err) { + content = data; + } + console.log("File read ", fileName); + callback.apply(null, [fileName, content]); + }); + } + + function extractContent(fileName, callback, extractFromMainContext) { + if (_dirtyFilesCache.indexOf(fileName) !== -1) { + // Ask the main thread context to provide the updated file content + extractFromMainContext.apply(fileName); + console.log("File read from main thread ", fileName); + } else { + _readFile(fileName, callback); + } + } + + exports.extractContent = extractContent; + exports.clearFilesCache = _clearDirtyFilesCache; + exports.updateFilesCache = _updateDirtyFilesCache; + +}()); diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 3c82d55f9f9..45b9e26b96e 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -39,28 +39,14 @@ var config = {}; var Tern = require("./node_modules/tern/lib/tern"), Infer = require("./node_modules/tern/lib/infer"); + var ExtractContent = require("./ExtractFileContent"); + var ternServer = null, inferenceTimeout; // Save the tern callbacks for when we get the contents of the file var fileCallBacks = {}; - - /** - * Provide the contents of the requested file to tern - * @param {string} name - the name of the file - * @param {Function} next - the function to call with the text of the file - * once it has been read in. - */ - function getFile(name, next) { - // save the callback - fileCallBacks[name] = next; - // post a message back to the main thread to get the file contents - self.postMessage({ - type: MessageIds.TERN_GET_FILE_MSG, - file: name - }); - } - + /** * Send a log message back from the node to the main thread * @private @@ -86,7 +72,7 @@ var config = {}; _log("Error thrown in tern_node domain:" + e.message + "\n" + e.stack); } } - + /** * Handle a response from the main thread providing the contents of a file * @param {string} file - the name of the file @@ -103,6 +89,29 @@ var config = {}; } delete fileCallBacks[file]; } + + /** + * Callback handle to request contents of a file from the main thread + * @param {string} file - the name of the file + */ + function _requestFileContent(name) { + self.postMessage({ + type: MessageIds.TERN_GET_FILE_MSG, + file: name + }); + } + + /** + * Provide the contents of the requested file to tern + * @param {string} name - the name of the file + * @param {Function} next - the function to call with the text of the file + * once it has been read in. + */ + function getFile(name, next) { + // save the callback + fileCallBacks[name] = next; + ExtractContent.extractContent(name, handleGetFile, _requestFileContent); + } /** * Create a new tern server. @@ -724,6 +733,10 @@ var config = {}; handleUpdateFile(request.path, request.text); } else if (type === MessageIds.SET_CONFIG) { setConfig(request.config); + } else if (type === MessageIds.TERN_UPDATE_DIRTY_FILE) { + ExtractContent.updateFilesCache(request.name, request.action); + } else if (type === MessageIds.TERN_CLEAR_DIRTY_FILES_LIST) { + ExtractContent.clearFilesCache(); } else { _log("Unknown message: " + JSON.stringify(request)); } From 02420b53b24c5d507d9aab0098ada58e65d60869 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 7 Dec 2015 20:14:14 +0530 Subject: [PATCH 08/25] Update dirty doc content in tern cache. --- src/extensions/default/JavaScriptCodeHints/ScopeManager.js | 6 ++++++ .../default/JavaScriptCodeHints/node/TernNodeDomain.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 88f3e75afb0..a948668074b 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -1092,6 +1092,8 @@ define(function (require, exports, module) { handleUpdateFile(response); } else if (type === MessageIds.TERN_INFERENCE_TIMEDOUT) { handleTimedOut(response); + } else if (type === MessageIds.TERN_WORKER_READY) { + moduleDeferred.resolveWith(null, [_ternNodeDomain]); } else { console.log("Tern Module: " + (response.log || response)); } @@ -1184,6 +1186,10 @@ define(function (require, exports, module) { isDocumentDirty = false; return; } + + if (previousDocument && previousDocument.isDirty) { + updateTernFile(previousDocument); + } isDocumentDirty = false; resolvedFiles = {}; diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 45b9e26b96e..d3dc397dadc 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -148,6 +148,10 @@ var config = {}; // If a server is already created just reset the analysis data if (ternServer) { ternServer.reset(); + ternServer.flush(); + Infer.resetGuessing(); + // tell the main thread we're ready to start processing again + self.postMessage({type: MessageIds.TERN_WORKER_READY}); } } @@ -664,6 +668,7 @@ var config = {}; // reset to get the best hints with the updated file. ternServer.reset(); + Infer.resetGuessing(); } /** From 873431e77a2df540fc4e96e3bf0b561040b90474 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Wed, 9 Dec 2015 16:34:44 +0530 Subject: [PATCH 09/25] Stability fix. --- .../node/ExtractFileContent.js | 2 +- .../node/TernNodeDomain.js | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js index 799ae04f7ae..13e80886a52 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -58,7 +58,7 @@ function extractContent(fileName, callback, extractFromMainContext) { if (_dirtyFilesCache.indexOf(fileName) !== -1) { // Ask the main thread context to provide the updated file content - extractFromMainContext.apply(fileName); + extractFromMainContext.apply(null, [fileName]); console.log("File read from main thread ", fileName); } else { _readFile(fileName, callback); diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index d3dc397dadc..8cd591f112c 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -22,6 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, node: true, regexp: true */ +/*global setImmediate */ var config = {}; @@ -110,7 +111,14 @@ var config = {}; function getFile(name, next) { // save the callback fileCallBacks[name] = next; - ExtractContent.extractContent(name, handleGetFile, _requestFileContent); + + setImmediate(function () { + try { + ExtractContent.extractContent(name, handleGetFile, _requestFileContent); + } catch (error) { + console.log(error); + } + }); } /** @@ -701,7 +709,7 @@ var config = {}; config = configUpdate; } - function invokeTernCommand(commandConfig) { + function _requestTernServer(commandConfig) { var file, text, offset, request = commandConfig, type = request.type; @@ -747,6 +755,14 @@ var config = {}; } } + function invokeTernCommand(commandConfig) { + try { + _requestTernServer(commandConfig); + } catch (error) { + console.warn(error); + } + } + function setInterface(msgInterface) { MessageIds = msgInterface.messageIds; } From ca7d68d942b5e01921179dc6318a838fb88bd58d Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 14 Dec 2015 11:56:54 +0530 Subject: [PATCH 10/25] Update submodule repo link. Adding JSDoc in ExtractFileContent --- .gitmodules | 2 +- .../node/ExtractFileContent.js | 34 ++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index c1fcf2b2faa..042dea2ae68 100644 --- a/.gitmodules +++ b/.gitmodules @@ -27,4 +27,4 @@ url = https://github.com/marijnh/acorn.git [submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern - url = https://github.com/marijnh/acorn.git + url = https://github.com/marijnh/tern.git diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js index 13e80886a52..03b080fc250 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -29,11 +29,22 @@ var fs = require("fs"), _dirtyFilesCache = []; - function _clearDirtyFilesCache() { + /** + * Clears the cache for dirty file paths + */ + function clearDirtyFilesCache() { _dirtyFilesCache = []; } - function _updateDirtyFilesCache(name, action) { + /** + * Updates the files cache with fullpath when dirty flag changes for a document + * If the doc is being marked as dirty then an entry is created in the cache + * If the doc is being marked as clean then the corresponsing entry gets cleared from cache + * + * @param {String} name - fullpath of the document + * @param {boolean} action - whether the document is dirty + */ + function updateDirtyFilesCache(name, action) { if (action) { _dirtyFilesCache.push(name); } else { @@ -44,6 +55,12 @@ } } + /** + * Extract content locally from the file system used fs.readFile() + * + * @param {String} fileName - fullpath of the document + * @param {Function} callback - callback handle to post the content back + */ function _readFile(fileName, callback) { fs.readFile(fileName, 'utf8', function (err, data) { var content = ""; @@ -55,6 +72,15 @@ }); } + /** + * Extracts file content for the given file name(1st param) and invokes the callback handle(2nd param) with + * extracted file content. Content can be extracted locally from the file system used fs.readFile() + * or conditionally from main context(brackets main thread) by using the 3rd param + * + * @param {String} fileName - fullpath of the document + * @param {Function} callback - callback handle to post the content back + * @param {Object} extractFromMainContext - content request handle wrapper from main thread + */ function extractContent(fileName, callback, extractFromMainContext) { if (_dirtyFilesCache.indexOf(fileName) !== -1) { // Ask the main thread context to provide the updated file content @@ -66,7 +92,7 @@ } exports.extractContent = extractContent; - exports.clearFilesCache = _clearDirtyFilesCache; - exports.updateFilesCache = _updateDirtyFilesCache; + exports.clearFilesCache = clearDirtyFilesCache; + exports.updateFilesCache = updateDirtyFilesCache; }()); From f726a28424a97d3df06631be8c57834ae57c14d2 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 14 Dec 2015 14:00:01 +0530 Subject: [PATCH 11/25] Submodule fix --- .gitmodules | 2 +- .../default/JavaScriptCodeHints/node/node_modules/tern | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9aeba746a79..f1b0bc9fe33 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,4 +30,4 @@ url = https://github.com/marijnh/acorn.git [submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern - url = https://github.com/marijnh/tern.git + url = https://github.com/ternjs/tern.git diff --git a/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern b/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern index 81483879d57..f585fe7d6f4 160000 --- a/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern +++ b/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern @@ -1 +1 @@ -Subproject commit 81483879d57cac77a236a59bf4cc6eb29a94a145 +Subproject commit f585fe7d6f4da28770fdd3dc7ddf88a39ad9b105 From 41037fd2c5bffd1f57a225e91ca41302dd8d47b5 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Wed, 11 Jan 2017 13:02:49 +0530 Subject: [PATCH 12/25] build error fix --- .../node/ExtractFileContent.js | 4 +- .../node/TernNodeDomain.js | 1410 ++++++++--------- 2 files changed, 707 insertions(+), 707 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js index 03b080fc250..684c545de3b 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -21,7 +21,9 @@ * */ -/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, node: true, regexp: true */ +/*eslint-env node */ +/*jslint node: true */ + (function () { "use strict"; diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 8cd591f112c..48d339ef797 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -21,808 +21,806 @@ * */ -/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, node: true, regexp: true */ -/*global setImmediate */ +/*eslint-env node */ +/*jslint node: true */ -var config = {}; -(function () { - "use strict"; - - var _domainManager; - var MessageIds; - var self = { - postMessage: function (data) { - _domainManager.emitEvent("TernNodeDomain", "data", [data]); - } - }; - - var Tern = require("./node_modules/tern/lib/tern"), - Infer = require("./node_modules/tern/lib/infer"); - - var ExtractContent = require("./ExtractFileContent"); - - var ternServer = null, - inferenceTimeout; - - // Save the tern callbacks for when we get the contents of the file - var fileCallBacks = {}; - - /** - * Send a log message back from the node to the main thread - * @private - * @param {string} msg - the log message - */ - function _log(msg) { - console.log(msg); - } - - /** - * Report exception - * @private - * @param {Error} e - the error object - */ - function _reportError(e, file) { - if (e instanceof Infer.TimedOut) { - // Post a message back to the main thread with timedout info - self.postMessage({ - type: MessageIds.TERN_INFERENCE_TIMEDOUT, - file: file - }); - } else { - _log("Error thrown in tern_node domain:" + e.message + "\n" + e.stack); - } - } - - /** - * Handle a response from the main thread providing the contents of a file - * @param {string} file - the name of the file - * @param {string} text - the contents of the file - */ - function handleGetFile(file, text) { - var next = fileCallBacks[file]; - if (next) { - try { - next(null, text); - } catch (e) { - _reportError(e, file); - } - } - delete fileCallBacks[file]; - } - - /** - * Callback handle to request contents of a file from the main thread - * @param {string} file - the name of the file - */ - function _requestFileContent(name) { - self.postMessage({ - type: MessageIds.TERN_GET_FILE_MSG, - file: name - }); + +"use strict"; + +var config = {}; +var _domainManager; +var MessageIds; +var self = { + postMessage: function (data) { + _domainManager.emitEvent("TernNodeDomain", "data", [data]); } +}; - /** - * Provide the contents of the requested file to tern - * @param {string} name - the name of the file - * @param {Function} next - the function to call with the text of the file - * once it has been read in. - */ - function getFile(name, next) { - // save the callback - fileCallBacks[name] = next; - - setImmediate(function () { - try { - ExtractContent.extractContent(name, handleGetFile, _requestFileContent); - } catch (error) { - console.log(error); - } +var Tern = require("./node_modules/tern/lib/tern"), + Infer = require("./node_modules/tern/lib/infer"); + +var ExtractContent = require("./ExtractFileContent"); + +var ternServer = null, + inferenceTimeout; + +// Save the tern callbacks for when we get the contents of the file +var fileCallBacks = {}; + +/** + * Send a log message back from the node to the main thread + * @private + * @param {string} msg - the log message + */ +function _log(msg) { + console.log(msg); +} + +/** + * Report exception + * @private + * @param {Error} e - the error object + */ +function _reportError(e, file) { + if (e instanceof Infer.TimedOut) { + // Post a message back to the main thread with timedout info + self.postMessage({ + type: MessageIds.TERN_INFERENCE_TIMEDOUT, + file: file }); + } else { + _log("Error thrown in tern_node domain:" + e.message + "\n" + e.stack); } +} - /** - * Create a new tern server. - * - * @param {Object} env - an Object with the environment, as read in from - * the json files in thirdparty/tern/defs - * @param {Array.} files - a list of filenames tern should be aware of - */ - function initTernServer(env, files) { - var ternOptions = { - defs: env, - async: true, - getFile: getFile, - plugins: {requirejs: {}, doc_comment: true, angular: true} - }; - - // If a server is already created just reset the analysis data - if (ternServer) { - ternServer.reset(); - } else { - ternServer = new Tern.Server(ternOptions); +/** + * Handle a response from the main thread providing the contents of a file + * @param {string} file - the name of the file + * @param {string} text - the contents of the file + */ +function handleGetFile(file, text) { + var next = fileCallBacks[file]; + if (next) { + try { + next(null, text); + } catch (e) { + _reportError(e, file); } + } + delete fileCallBacks[file]; +} - files.forEach(function (file) { - ternServer.addFile(file); - }); +/** + * Callback handle to request contents of a file from the main thread + * @param {string} file - the name of the file + */ +function _requestFileContent(name) { + self.postMessage({ + type: MessageIds.TERN_GET_FILE_MSG, + file: name + }); +} + +/** + * Provide the contents of the requested file to tern + * @param {string} name - the name of the file + * @param {Function} next - the function to call with the text of the file + * once it has been read in. + */ +function getFile(name, next) { + // save the callback + fileCallBacks[name] = next; - } - - /** - * Resets an existing tern server. - */ - function resetTernServer() { - // If a server is already created just reset the analysis data - if (ternServer) { - ternServer.reset(); - ternServer.flush(); - Infer.resetGuessing(); - // tell the main thread we're ready to start processing again - self.postMessage({type: MessageIds.TERN_WORKER_READY}); + setImmediate(function () { + try { + ExtractContent.extractContent(name, handleGetFile, _requestFileContent); + } catch (error) { + console.log(error); } + }); +} + +/** + * Create a new tern server. + * + * @param {Object} env - an Object with the environment, as read in from + * the json files in thirdparty/tern/defs + * @param {Array.} files - a list of filenames tern should be aware of + */ +function initTernServer(env, files) { + var ternOptions = { + defs: env, + async: true, + getFile: getFile, + plugins: {requirejs: {}, doc_comment: true, angular: true} + }; + + // If a server is already created just reset the analysis data + if (ternServer) { + ternServer.reset(); + } else { + ternServer = new Tern.Server(ternOptions); } - /** - * Create a "empty" update object. - * - * @param {string} path - full path of the file. - * @return {{type: string, name: string, offsetLines: number, text: string}} - - * "empty" update. + files.forEach(function (file) { + ternServer.addFile(file); + }); - */ - function createEmptyUpdate(path) { - return {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, - name: path, - offsetLines: 0, - text: ""}; +} + +/** + * Resets an existing tern server. + */ +function resetTernServer() { + // If a server is already created just reset the analysis data + if (ternServer) { + ternServer.reset(); + ternServer.flush(); + Infer.resetGuessing(); + // tell the main thread we're ready to start processing again + self.postMessage({type: MessageIds.TERN_WORKER_READY}); } +} - /** - * Build an object that can be used as a request to tern. - * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {string} query - the type of request being made - * @param {{line: number, ch: number}} offset - - */ - function buildRequest(fileInfo, query, offset) { - query = {type: query}; - query.start = offset; - query.end = offset; - query.file = (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) ? "#0" : fileInfo.name; - query.filter = false; - query.sort = false; - query.depths = true; - query.guess = true; - query.origins = true; - query.types = true; - query.expandWordForward = false; - query.lineCharPositions = true; - query.docs = true; - query.urls = true; - - var request = {query: query, files: [], offset: offset, timeout: inferenceTimeout}; - if (fileInfo.type !== MessageIds.TERN_FILE_INFO_TYPE_EMPTY) { - request.files.push(fileInfo); - } +/** + * Create a "empty" update object. + * + * @param {string} path - full path of the file. + * @return {{type: string, name: string, offsetLines: number, text: string}} - + * "empty" update. - return request; + */ +function createEmptyUpdate(path) { + return {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, + name: path, + offsetLines: 0, + text: ""}; +} + +/** + * Build an object that can be used as a request to tern. + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {string} query - the type of request being made + * @param {{line: number, ch: number}} offset - + */ +function buildRequest(fileInfo, query, offset) { + query = {type: query}; + query.start = offset; + query.end = offset; + query.file = (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) ? "#0" : fileInfo.name; + query.filter = false; + query.sort = false; + query.depths = true; + query.guess = true; + query.origins = true; + query.types = true; + query.expandWordForward = false; + query.lineCharPositions = true; + query.docs = true; + query.urls = true; + + var request = {query: query, files: [], offset: offset, timeout: inferenceTimeout}; + if (fileInfo.type !== MessageIds.TERN_FILE_INFO_TYPE_EMPTY) { + request.files.push(fileInfo); } - /** - * Get definition location - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - the offset into the - * file for cursor - */ - function getJumptoDef(fileInfo, offset) { - var request = buildRequest(fileInfo, "definition", offset); - // request.query.typeOnly = true; // FIXME: tern doesn't work exactly right yet. + return request; +} + +/** + * Get definition location + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - the offset into the + * file for cursor + */ +function getJumptoDef(fileInfo, offset) { + var request = buildRequest(fileInfo, "definition", offset); + // request.query.typeOnly = true; // FIXME: tern doesn't work exactly right yet. + + try { + ternServer.request(request, function (error, data) { + if (error) { + _log("Error returned from Tern 'definition' request: " + error); + self.postMessage({type: MessageIds.TERN_JUMPTODEF_MSG, file: fileInfo.name, offset: offset}); + return; + } + var response = {type: MessageIds.TERN_JUMPTODEF_MSG, + file: fileInfo.name, + resultFile: data.file, + offset: offset, + start: data.start, + end: data.end + }; - try { + request = buildRequest(fileInfo, "type", offset); + // See if we can tell if the reference is to a Function type ternServer.request(request, function (error, data) { - if (error) { - _log("Error returned from Tern 'definition' request: " + error); - self.postMessage({type: MessageIds.TERN_JUMPTODEF_MSG, file: fileInfo.name, offset: offset}); - return; + if (!error) { + response.isFunction = data.type.length > 2 && data.type.substring(0, 2) === "fn"; } - var response = {type: MessageIds.TERN_JUMPTODEF_MSG, - file: fileInfo.name, - resultFile: data.file, - offset: offset, - start: data.start, - end: data.end - }; - - request = buildRequest(fileInfo, "type", offset); - // See if we can tell if the reference is to a Function type - ternServer.request(request, function (error, data) { - if (!error) { - response.isFunction = data.type.length > 2 && data.type.substring(0, 2) === "fn"; - } - - // Post a message back to the main thread with the definition - self.postMessage(response); - }); + // Post a message back to the main thread with the definition + self.postMessage(response); }); - } catch (e) { - _reportError(e, fileInfo.name); - } - } - /** - * Get all the known properties for guessing. - * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - - * the offset into the file where we want completions for - * @param {string} type - the type of the message to reply with. - */ - function getTernProperties(fileInfo, offset, type) { + }); + } catch (e) { + _reportError(e, fileInfo.name); + } +} + +/** + * Get all the known properties for guessing. + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - + * the offset into the file where we want completions for + * @param {string} type - the type of the message to reply with. + */ +function getTernProperties(fileInfo, offset, type) { + + var request = buildRequest(fileInfo, "properties", offset), + i; + //_log("tern properties: request " + request.type + dir + " " + file); + try { + ternServer.request(request, function (error, data) { + var properties = []; + if (error) { + _log("Error returned from Tern 'properties' request: " + error); + } else { + //_log("tern properties: completions = " + data.completions.length); + for (i = 0; i < data.completions.length; ++i) { + var property = data.completions[i]; + properties.push({value: property, type: property.type, guess: true}); + } + } - var request = buildRequest(fileInfo, "properties", offset), - i; - //_log("tern properties: request " + request.type + dir + " " + file); - try { - ternServer.request(request, function (error, data) { - var properties = []; - if (error) { - _log("Error returned from Tern 'properties' request: " + error); - } else { - //_log("tern properties: completions = " + data.completions.length); - for (i = 0; i < data.completions.length; ++i) { - var property = data.completions[i]; - properties.push({value: property, type: property.type, guess: true}); - } + // Post a message back to the main thread with the completions + self.postMessage({type: type, + file: fileInfo.name, + offset: offset, + properties: properties + }); + }); + } catch (e) { + _reportError(e, fileInfo.name); + } +} + +/** + * Get the completions for the given offset + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - + * the offset into the file where we want completions for + * @param {boolean} isProperty - true if getting a property hint, + * otherwise getting an identifier hint. + */ +function getTernHints(fileInfo, offset, isProperty) { + var request = buildRequest(fileInfo, "completions", offset), + i; + //_log("request " + dir + " " + file + " " + offset /*+ " " + text */); + try { + ternServer.request(request, function (error, data) { + var completions = []; + if (error) { + _log("Error returned from Tern 'completions' request: " + error); + } else { + //_log("found " + data.completions.length + " for " + file + "@" + offset); + for (i = 0; i < data.completions.length; ++i) { + var completion = data.completions[i]; + completions.push({value: completion.name, type: completion.type, depth: completion.depth, + guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}); } + } + if (completions.length > 0 || !isProperty) { // Post a message back to the main thread with the completions - self.postMessage({type: type, - file: fileInfo.name, - offset: offset, - properties: properties + self.postMessage({type: MessageIds.TERN_COMPLETIONS_MSG, + file: fileInfo.name, + offset: offset, + completions: completions }); - }); - } catch (e) { - _reportError(e, fileInfo.name); - } + } else { + // if there are no completions, then get all the properties + getTernProperties(fileInfo, offset, MessageIds.TERN_COMPLETIONS_MSG); + } + }); + } catch (e) { + _reportError(e, fileInfo.name); } +} + +/** + * Given a Tern type object, convert it to an array of Objects, where each object describes + * a parameter. + * + * @param {!Infer.Fn} inferFnType - type to convert. + * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. + */ +function getParameters(inferFnType) { + + // work around define functions before use warning. + var recordTypeToString, inferTypeToString, processInferFnTypeParameters, inferFnTypeToString; /** - * Get the completions for the given offset + * Convert an infer array type to a string. + * + * Formatted using google closure style. For example: + * + * "Array." + * + * @param {Infer.Arr} inferArrType + * + * @return {string} - array formatted in google closure style. * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - - * the offset into the file where we want completions for - * @param {boolean} isProperty - true if getting a property hint, - * otherwise getting an identifier hint. */ - function getTernHints(fileInfo, offset, isProperty) { - var request = buildRequest(fileInfo, "completions", offset), - i; - //_log("request " + dir + " " + file + " " + offset /*+ " " + text */); - try { - ternServer.request(request, function (error, data) { - var completions = []; - if (error) { - _log("Error returned from Tern 'completions' request: " + error); - } else { - //_log("found " + data.completions.length + " for " + file + "@" + offset); - for (i = 0; i < data.completions.length; ++i) { - var completion = data.completions[i]; - completions.push({value: completion.name, type: completion.type, depth: completion.depth, - guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}); - } - } + function inferArrTypeToString(inferArrType) { + var result = "Array.<"; - if (completions.length > 0 || !isProperty) { - // Post a message back to the main thread with the completions - self.postMessage({type: MessageIds.TERN_COMPLETIONS_MSG, - file: fileInfo.name, - offset: offset, - completions: completions - }); - } else { - // if there are no completions, then get all the properties - getTernProperties(fileInfo, offset, MessageIds.TERN_COMPLETIONS_MSG); - } - }); - } catch (e) { - _reportError(e, fileInfo.name); + inferArrType.props[""].types.forEach(function (value, i) { + if (i > 0) { + result += ", "; + } + result += inferTypeToString(value); + }); + + // workaround case where types is zero length + if (inferArrType.props[""].types.length === 0) { + result += "Object"; } + result += ">"; + + return result; } /** - * Given a Tern type object, convert it to an array of Objects, where each object describes - * a parameter. + * Convert properties to a record type annotation. * - * @param {!Infer.Fn} inferFnType - type to convert. - * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. + * @param {Object} props + * @return {string} - record type annotation */ - function getParameters(inferFnType) { - - // work around define functions before use warning. - var recordTypeToString, inferTypeToString, processInferFnTypeParameters, inferFnTypeToString; - - /** - * Convert an infer array type to a string. - * - * Formatted using google closure style. For example: - * - * "Array." - * - * @param {Infer.Arr} inferArrType - * - * @return {string} - array formatted in google closure style. - * - */ - function inferArrTypeToString(inferArrType) { - var result = "Array.<"; - - inferArrType.props[""].types.forEach(function (value, i) { - if (i > 0) { + recordTypeToString = function (props) { + var result = "{", + first = true, + prop; + + for (prop in props) { + if (Object.prototype.hasOwnProperty.call(props, prop)) { + if (!first) { result += ", "; } - result += inferTypeToString(value); - }); - // workaround case where types is zero length - if (inferArrType.props[""].types.length === 0) { - result += "Object"; + first = false; + result += prop + ": " + inferTypeToString(props[prop]); } - result += ">"; - - return result; } - /** - * Convert properties to a record type annotation. - * - * @param {Object} props - * @return {string} - record type annotation - */ - recordTypeToString = function (props) { - var result = "{", - first = true, - prop; - - for (prop in props) { - if (Object.prototype.hasOwnProperty.call(props, prop)) { - if (!first) { - result += ", "; - } - - first = false; - result += prop + ": " + inferTypeToString(props[prop]); - } - } + result += "}"; - result += "}"; - - return result; - }; - - /** - * Convert an infer type to a string. - * - * @param {*} inferType - one of the Infer's types; Infer.Prim, Infer.Arr, Infer.ANull. Infer.Fn functions are - * not handled here. - * - * @return {string} - * - */ - inferTypeToString = function (inferType) { - var result; - - if (inferType instanceof Infer.AVal) { - inferType = inferType.types[0]; - } + return result; + }; - if (inferType instanceof Infer.Prim) { - result = inferType.toString(); - if (result === "string") { - result = "String"; - } else if (result === "number") { - result = "Number"; - } else if (result === "boolean") { - result = "Boolean"; - } - } else if (inferType instanceof Infer.Arr) { - result = inferArrTypeToString(inferType); - } else if (inferType instanceof Infer.Fn) { - result = inferFnTypeToString(inferType); - } else if (inferType instanceof Infer.Obj) { - if (inferType.name === undefined) { - result = recordTypeToString(inferType.props); - } else { - result = inferType.name; - } - } else { - result = "Object"; - } + /** + * Convert an infer type to a string. + * + * @param {*} inferType - one of the Infer's types; Infer.Prim, Infer.Arr, Infer.ANull. Infer.Fn functions are + * not handled here. + * + * @return {string} + * + */ + inferTypeToString = function (inferType) { + var result; - return result; - }; - - /** - * Format the given parameter array. Handles separators between - * parameters, syntax for optional parameters, and the order of the - * parameter type and parameter name. - * - * @param {!Array.<{name: string, type: string, isOptional: boolean}>} params - - * array of parameter descriptors - * @param {function(string)=} appendSeparators - callback function to append separators. - * The separator is passed to the callback. - * @param {function(string, number)=} appendParameter - callback function to append parameter. - * The formatted parameter type and name is passed to the callback along with the - * current index of the parameter. - * @param {boolean=} typesOnly - only show parameter types. The - * default behavior is to include both parameter names and types. - * @return {string} - formatted parameter hint - */ - function formatParameterHint(params, appendSeparators, appendParameter, typesOnly) { - var result = "", - pendingOptional = false; - - params.forEach(function (value, i) { - var param = value.type, - separators = ""; - - if (value.isOptional) { - // if an optional param is following by an optional parameter, then - // terminate the bracket. Otherwise enclose a required parameter - // in the same bracket. - if (pendingOptional) { - separators += "]"; - } - - pendingOptional = true; - } + if (inferType instanceof Infer.AVal) { + inferType = inferType.types[0]; + } - if (i > 0) { - separators += ", "; - } + if (inferType instanceof Infer.Prim) { + result = inferType.toString(); + if (result === "string") { + result = "String"; + } else if (result === "number") { + result = "Number"; + } else if (result === "boolean") { + result = "Boolean"; + } + } else if (inferType instanceof Infer.Arr) { + result = inferArrTypeToString(inferType); + } else if (inferType instanceof Infer.Fn) { + result = inferFnTypeToString(inferType); + } else if (inferType instanceof Infer.Obj) { + if (inferType.name === undefined) { + result = recordTypeToString(inferType.props); + } else { + result = inferType.name; + } + } else { + result = "Object"; + } - if (value.isOptional) { - separators += "["; - } + return result; + }; - if (appendSeparators) { - appendSeparators(separators); + /** + * Format the given parameter array. Handles separators between + * parameters, syntax for optional parameters, and the order of the + * parameter type and parameter name. + * + * @param {!Array.<{name: string, type: string, isOptional: boolean}>} params - + * array of parameter descriptors + * @param {function(string)=} appendSeparators - callback function to append separators. + * The separator is passed to the callback. + * @param {function(string, number)=} appendParameter - callback function to append parameter. + * The formatted parameter type and name is passed to the callback along with the + * current index of the parameter. + * @param {boolean=} typesOnly - only show parameter types. The + * default behavior is to include both parameter names and types. + * @return {string} - formatted parameter hint + */ + function formatParameterHint(params, appendSeparators, appendParameter, typesOnly) { + var result = "", + pendingOptional = false; + + params.forEach(function (value, i) { + var param = value.type, + separators = ""; + + if (value.isOptional) { + // if an optional param is following by an optional parameter, then + // terminate the bracket. Otherwise enclose a required parameter + // in the same bracket. + if (pendingOptional) { + separators += "]"; } - result += separators; - - if (!typesOnly) { - param += " " + value.name; - } + pendingOptional = true; + } - if (appendParameter) { - appendParameter(param, i); - } + if (i > 0) { + separators += ", "; + } - result += param; + if (value.isOptional) { + separators += "["; + } - }); + if (appendSeparators) { + appendSeparators(separators); + } - if (pendingOptional) { - if (appendSeparators) { - appendSeparators("]"); - } + result += separators; - result += "]"; + if (!typesOnly) { + param += " " + value.name; } - return result; - } - - /** - * Convert an infer function type to a Google closure type string. - * - * @param {Infer.Fn} inferType - type to convert. - * @return {string} - function type as a string. - */ - inferFnTypeToString = function (inferType) { - var result = "function(", - params = processInferFnTypeParameters(inferType); - - result += /*HintUtils2.*/formatParameterHint(params, null, null, true); - if (inferType.retval) { - result += "):"; - result += inferTypeToString(inferType.retval); + if (appendParameter) { + appendParameter(param, i); } - return result; - }; - - /** - * Convert an infer function type to string. - * - * @param {*} inferType - one of the Infer's types; Infer.Fn, Infer.Prim, Infer.Arr, Infer.ANull - * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. - */ - processInferFnTypeParameters = function (inferType) { - var params = [], - i; - - for (i = 0; i < inferType.args.length; i++) { - var param = {}, - name = inferType.argNames[i], - type = inferType.args[i]; - - if (!name) { - name = "param" + (i + 1); - } + result += param; - if (name[name.length - 1] === "?") { - name = name.substring(0, name.length - 1); - param.isOptional = true; - } + }); - param.name = name; - param.type = inferTypeToString(type); - params.push(param); + if (pendingOptional) { + if (appendSeparators) { + appendSeparators("]"); } - return params; - }; + result += "]"; + } - return processInferFnTypeParameters(inferFnType); + return result; } /** - * Get the function type for the given offset + * Convert an infer function type to a Google closure type string. * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - - * the offset into the file where we want completions for + * @param {Infer.Fn} inferType - type to convert. + * @return {string} - function type as a string. */ - function handleFunctionType(fileInfo, offset) { - var request = buildRequest(fileInfo, "type", offset), - error; - - request.query.preferFunction = true; - - var fnType = ""; - try { - ternServer.request(request, function (ternError, data) { - - if (ternError) { - _log("Error for Tern request: \n" + JSON.stringify(request) + "\n" + ternError); - error = ternError.toString(); - } else { - var file = ternServer.findFile(fileInfo.name); - - // convert query from partial to full offsets - var newOffset = offset; - if (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) { - newOffset = {line: offset.line + fileInfo.offsetLines, ch: offset.ch}; - } - - request = buildRequest(createEmptyUpdate(fileInfo.name), "type", newOffset); - - var expr = Tern.findQueryExpr(file, request.query); - Infer.resetGuessing(); - var type = Infer.expressionType(expr); - type = type.getFunctionType() || type.getType(); - - if (type) { - fnType = getParameters(type); - } else { - ternError = "No parameter type found"; - _log(ternError); - } - } - }); - } catch (e) { - _reportError(e, fileInfo.name); + inferFnTypeToString = function (inferType) { + var result = "function(", + params = processInferFnTypeParameters(inferType); + + result += /*HintUtils2.*/formatParameterHint(params, null, null, true); + if (inferType.retval) { + result += "):"; + result += inferTypeToString(inferType.retval); } - // Post a message back to the main thread with the completions - self.postMessage({type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG, - file: fileInfo.name, - offset: offset, - fnType: fnType, - error: error - }); - } + return result; + }; /** - * Add an array of files to tern. + * Convert an infer function type to string. * - * @param {Array.} files - each string in the array is the full - * path of a file. + * @param {*} inferType - one of the Infer's types; Infer.Fn, Infer.Prim, Infer.Arr, Infer.ANull + * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. */ - function handleAddFiles(files) { - files.forEach(function (file) { - ternServer.addFile(file); + processInferFnTypeParameters = function (inferType) { + var params = [], + i; + + for (i = 0; i < inferType.args.length; i++) { + var param = {}, + name = inferType.argNames[i], + type = inferType.args[i]; + + if (!name) { + name = "param" + (i + 1); + } + + if (name[name.length - 1] === "?") { + name = name.substring(0, name.length - 1); + param.isOptional = true; + } + + param.name = name; + param.type = inferTypeToString(type); + params.push(param); + } + + return params; + }; + + return processInferFnTypeParameters(inferFnType); +} + +/** + * Get the function type for the given offset + * + * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo + * - type of update, name of file, and the text of the update. + * For "full" updates, the whole text of the file is present. For "part" updates, + * the changed portion of the text. For "empty" updates, the file has not been modified + * and the text is empty. + * @param {{line: number, ch: number}} offset - + * the offset into the file where we want completions for + */ +function handleFunctionType(fileInfo, offset) { + var request = buildRequest(fileInfo, "type", offset), + error; + + request.query.preferFunction = true; + + var fnType = ""; + try { + ternServer.request(request, function (ternError, data) { + + if (ternError) { + _log("Error for Tern request: \n" + JSON.stringify(request) + "\n" + ternError); + error = ternError.toString(); + } else { + var file = ternServer.findFile(fileInfo.name); + + // convert query from partial to full offsets + var newOffset = offset; + if (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) { + newOffset = {line: offset.line + fileInfo.offsetLines, ch: offset.ch}; + } + + request = buildRequest(createEmptyUpdate(fileInfo.name), "type", newOffset); + + var expr = Tern.findQueryExpr(file, request.query); + Infer.resetGuessing(); + var type = Infer.expressionType(expr); + type = type.getFunctionType() || type.getType(); + + if (type) { + fnType = getParameters(type); + } else { + ternError = "No parameter type found"; + _log(ternError); + } + } }); + } catch (e) { + _reportError(e, fileInfo.name); } - /** - * Update the context of a file in tern. - * - * @param {string} path - full path of file. - * @param {string} text - content of the file. - */ - function handleUpdateFile(path, text) { + // Post a message back to the main thread with the completions + self.postMessage({type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG, + file: fileInfo.name, + offset: offset, + fnType: fnType, + error: error + }); +} - ternServer.addFile(path, text); +/** + * Add an array of files to tern. + * + * @param {Array.} files - each string in the array is the full + * path of a file. + */ +function handleAddFiles(files) { + files.forEach(function (file) { + ternServer.addFile(file); + }); +} + +/** + * Update the context of a file in tern. + * + * @param {string} path - full path of file. + * @param {string} text - content of the file. + */ +function handleUpdateFile(path, text) { - self.postMessage({type: MessageIds.TERN_UPDATE_FILE_MSG, - path: path - }); + ternServer.addFile(path, text); - // reset to get the best hints with the updated file. - ternServer.reset(); - Infer.resetGuessing(); - } + self.postMessage({type: MessageIds.TERN_UPDATE_FILE_MSG, + path: path + }); - /** - * Make a completions request to tern to force tern to resolve files - * and create a fast first lookup for the user. - * @param {string} path - the path of the file - */ - function handlePrimePump(path) { - var fileInfo = createEmptyUpdate(path), - request = buildRequest(fileInfo, "completions", {line: 0, ch: 0}); + // reset to get the best hints with the updated file. + ternServer.reset(); + Infer.resetGuessing(); +} - try { - ternServer.request(request, function (error, data) { - // Post a message back to the main thread - self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG, - path: path - }); - }); - } catch (e) { - _reportError(e, path); - } +/** + * Make a completions request to tern to force tern to resolve files + * and create a fast first lookup for the user. + * @param {string} path - the path of the file + */ +function handlePrimePump(path) { + var fileInfo = createEmptyUpdate(path), + request = buildRequest(fileInfo, "completions", {line: 0, ch: 0}); + + try { + ternServer.request(request, function (error, data) { + // Post a message back to the main thread + self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG, + path: path + }); + }); + } catch (e) { + _reportError(e, path); } +} - /** - * Updates the configuration, typically for debugging purposes. - * - * @param {Object} configUpdate new configuration - */ - function setConfig(configUpdate) { - config = configUpdate; +/** + * Updates the configuration, typically for debugging purposes. + * + * @param {Object} configUpdate new configuration + */ +function setConfig(configUpdate) { + config = configUpdate; +} + +function _requestTernServer(commandConfig) { + var file, text, offset, + request = commandConfig, + type = request.type; + if (config.debug) { + _log("Message received " + type); } - function _requestTernServer(commandConfig) { - var file, text, offset, - request = commandConfig, - type = request.type; - if (config.debug) { - _log("Message received " + type); - } - - if (type === MessageIds.TERN_INIT_MSG) { - var env = request.env, - files = request.files; - inferenceTimeout = request.timeout; - initTernServer(env, files); - } else if (type === MessageIds.TERN_COMPLETIONS_MSG) { - offset = request.offset; - getTernHints(request.fileInfo, offset, request.isProperty); - } else if (type === MessageIds.TERN_GET_FILE_MSG) { - file = request.file; - text = request.text; - handleGetFile(file, text); - } else if (type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { - offset = request.offset; - handleFunctionType(request.fileInfo, offset); - } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { - offset = request.offset; - getJumptoDef(request.fileInfo, offset); - } else if (type === MessageIds.TERN_ADD_FILES_MSG) { - handleAddFiles(request.files); - } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { - handlePrimePump(request.path); - } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { - offset = request.offset; - getTernProperties(request.fileInfo, offset, MessageIds.TERN_GET_GUESSES_MSG); - } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { - handleUpdateFile(request.path, request.text); - } else if (type === MessageIds.SET_CONFIG) { - setConfig(request.config); - } else if (type === MessageIds.TERN_UPDATE_DIRTY_FILE) { - ExtractContent.updateFilesCache(request.name, request.action); - } else if (type === MessageIds.TERN_CLEAR_DIRTY_FILES_LIST) { - ExtractContent.clearFilesCache(); - } else { - _log("Unknown message: " + JSON.stringify(request)); - } + if (type === MessageIds.TERN_INIT_MSG) { + var env = request.env, + files = request.files; + inferenceTimeout = request.timeout; + initTernServer(env, files); + } else if (type === MessageIds.TERN_COMPLETIONS_MSG) { + offset = request.offset; + getTernHints(request.fileInfo, offset, request.isProperty); + } else if (type === MessageIds.TERN_GET_FILE_MSG) { + file = request.file; + text = request.text; + handleGetFile(file, text); + } else if (type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { + offset = request.offset; + handleFunctionType(request.fileInfo, offset); + } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { + offset = request.offset; + getJumptoDef(request.fileInfo, offset); + } else if (type === MessageIds.TERN_ADD_FILES_MSG) { + handleAddFiles(request.files); + } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { + handlePrimePump(request.path); + } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { + offset = request.offset; + getTernProperties(request.fileInfo, offset, MessageIds.TERN_GET_GUESSES_MSG); + } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { + handleUpdateFile(request.path, request.text); + } else if (type === MessageIds.SET_CONFIG) { + setConfig(request.config); + } else if (type === MessageIds.TERN_UPDATE_DIRTY_FILE) { + ExtractContent.updateFilesCache(request.name, request.action); + } else if (type === MessageIds.TERN_CLEAR_DIRTY_FILES_LIST) { + ExtractContent.clearFilesCache(); + } else { + _log("Unknown message: " + JSON.stringify(request)); } +} - function invokeTernCommand(commandConfig) { - try { - _requestTernServer(commandConfig); - } catch (error) { - console.warn(error); - } +function invokeTernCommand(commandConfig) { + try { + _requestTernServer(commandConfig); + } catch (error) { + console.warn(error); } +} - function setInterface(msgInterface) { - MessageIds = msgInterface.messageIds; - } +function setInterface(msgInterface) { + MessageIds = msgInterface.messageIds; +} - /** - * Initialize the test domain with commands and events related to find in files. - * @param {DomainManager} domainManager The DomainManager for the TernNodeDomain - */ - function init(domainManager) { - if (!domainManager.hasDomain("TernNodeDomain")) { - domainManager.registerDomain("TernNodeDomain", {major: 0, minor: 1}); - } - - _domainManager = domainManager; - - domainManager.registerCommand( - "TernNodeDomain", // domain name - "invokeTernCommand", // command name - invokeTernCommand, // command handler function - false, // this command is synchronous in Node - "Invokes a tern command on node", - [{name: "commandConfig", // parameters - type: "object", - description: "Object containing tern command configuration"}] - ); - - domainManager.registerCommand( - "TernNodeDomain", // domain name - "setInterface", // command name - setInterface, // command handler function - false, // this command is synchronous in Node - "Sets the shared message interface", - [{name: "msgInterface", // parameters - type: "object", - description: "Object containing messageId enums"}] - ); - - domainManager.registerCommand( - "TernNodeDomain", // domain name - "resetTernServer", // command name - resetTernServer, // command handler function - true, // this command is synchronous in Node - "Resets an existing tern server" - ); - - domainManager.registerEvent( - "TernNodeDomain", // domain name - "data", // event name - [ - { - name: "data", - type: "Object", - description: "data to be returned to main thread" - } - ] - ); - - console.log("Tern Node ready!"); + /** + * Initialize the test domain with commands and events related to find in files. + * @param {DomainManager} domainManager The DomainManager for the TernNodeDomain + */ +function init(domainManager) { + if (!domainManager.hasDomain("TernNodeDomain")) { + domainManager.registerDomain("TernNodeDomain", {major: 0, minor: 1}); } - - exports.init = init; - -}()); + + _domainManager = domainManager; + + domainManager.registerCommand( + "TernNodeDomain", // domain name + "invokeTernCommand", // command name + invokeTernCommand, // command handler function + false, // this command is synchronous in Node + "Invokes a tern command on node", + [{name: "commandConfig", // parameters + type: "object", + description: "Object containing tern command configuration"}] + ); + + domainManager.registerCommand( + "TernNodeDomain", // domain name + "setInterface", // command name + setInterface, // command handler function + false, // this command is synchronous in Node + "Sets the shared message interface", + [{name: "msgInterface", // parameters + type: "object", + description: "Object containing messageId enums"}] + ); + + domainManager.registerCommand( + "TernNodeDomain", // domain name + "resetTernServer", // command name + resetTernServer, // command handler function + true, // this command is synchronous in Node + "Resets an existing tern server" + ); + + domainManager.registerEvent( + "TernNodeDomain", // domain name + "data", // event name + [ + { + name: "data", + type: "Object", + description: "data to be returned to main thread" + } + ] + ); + + console.log("Tern Node ready!"); +} + +exports.init = init; From 87a1d951a1ac822bad937f0d82f82310c5767d85 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Wed, 11 Jan 2017 13:15:40 +0530 Subject: [PATCH 13/25] Usage of global use strict --- .../default/JavaScriptCodeHints/node/ExtractFileContent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js index 684c545de3b..e95ffc5a9a1 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -24,10 +24,10 @@ /*eslint-env node */ /*jslint node: true */ +"use strict"; (function () { - "use strict"; - + var fs = require("fs"), _dirtyFilesCache = []; From cb86b4d71bb9e4705bd048009f722e7a813453e5 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Tue, 17 Jan 2017 15:51:11 +0530 Subject: [PATCH 14/25] Node dependencies --- .../default/JavaScriptCodeHints/node/package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/extensions/default/JavaScriptCodeHints/node/package.json diff --git a/src/extensions/default/JavaScriptCodeHints/node/package.json b/src/extensions/default/JavaScriptCodeHints/node/package.json new file mode 100644 index 00000000000..3f665e0693a --- /dev/null +++ b/src/extensions/default/JavaScriptCodeHints/node/package.json @@ -0,0 +1,10 @@ +{ + "name": "brackets-javascript-code-hints", + "dependencies": { + "acorn": "3.3.0", + "tern": "0.20.0" + }, + "scripts": { + "postinstall": "node ./fix-acorn" + } +} From 9a179b0a586c6e801a6d8012f4da598feb4fb2ed Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 2 Feb 2017 13:11:12 +0530 Subject: [PATCH 15/25] Remove redundant module defs --- .gitmodules | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitmodules b/.gitmodules index 685eecf6f37..403668b6862 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,9 +16,3 @@ [submodule "src/extensions/default/JSLint/thirdparty/jslint"] path = src/extensions/default/JSLint/thirdparty/jslint url = https://github.com/peterflynn/JSLint.git -[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn"] - path = src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn - url = https://github.com/marijnh/acorn.git -[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] - path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern - url = https://github.com/ternjs/tern.git From e6b2592da2565276c32ceb5cebf31c6b362a17e3 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 2 Feb 2017 13:12:27 +0530 Subject: [PATCH 16/25] Remove redundant script --- src/extensions/default/JavaScriptCodeHints/node/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/package.json b/src/extensions/default/JavaScriptCodeHints/node/package.json index 3f665e0693a..6263e368930 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/package.json +++ b/src/extensions/default/JavaScriptCodeHints/node/package.json @@ -3,8 +3,5 @@ "dependencies": { "acorn": "3.3.0", "tern": "0.20.0" - }, - "scripts": { - "postinstall": "node ./fix-acorn" } } From eba850ecac4679ef5c7c09a2bc8cd21830862bd8 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 2 Feb 2017 13:26:05 +0530 Subject: [PATCH 17/25] src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn --- .gitmodules | 8 +------- .../default/JavaScriptCodeHints/node/node_modules/acorn | 1 - .../default/JavaScriptCodeHints/node/node_modules/tern | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 160000 src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn delete mode 160000 src/extensions/default/JavaScriptCodeHints/node/node_modules/tern diff --git a/.gitmodules b/.gitmodules index 685eecf6f37..3183932d900 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,10 +15,4 @@ url = https://github.com/requirejs/i18n.git [submodule "src/extensions/default/JSLint/thirdparty/jslint"] path = src/extensions/default/JSLint/thirdparty/jslint - url = https://github.com/peterflynn/JSLint.git -[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn"] - path = src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn - url = https://github.com/marijnh/acorn.git -[submodule "src/extensions/default/JavaScriptCodeHints/node/node_modules/tern"] - path = src/extensions/default/JavaScriptCodeHints/node/node_modules/tern - url = https://github.com/ternjs/tern.git + url = https://github.com/peterflynn/JSLint.git diff --git a/src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn b/src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn deleted file mode 160000 index 81483879d57..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/node/node_modules/acorn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 81483879d57cac77a236a59bf4cc6eb29a94a145 diff --git a/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern b/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern deleted file mode 160000 index f585fe7d6f4..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/node/node_modules/tern +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f585fe7d6f4da28770fdd3dc7ddf88a39ad9b105 From 205c8dad236c0cd933ba958fa1047e99299a8c99 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Wed, 15 Feb 2017 09:18:31 +0530 Subject: [PATCH 18/25] Update .gitmodules To make it even with master --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 3183932d900..403668b6862 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = https://github.com/requirejs/i18n.git [submodule "src/extensions/default/JSLint/thirdparty/jslint"] path = src/extensions/default/JSLint/thirdparty/jslint - url = https://github.com/peterflynn/JSLint.git + url = https://github.com/peterflynn/JSLint.git From e166034dfb8503bb5d012dbb02569fab5d3468b8 Mon Sep 17 00:00:00 2001 From: Saurabh Kathpalia Date: Tue, 7 Mar 2017 16:59:03 +0530 Subject: [PATCH 19/25] Addressed review comments --- .../JavaScriptCodeHints/ScopeManager.js | 22 +-- .../node/ExtractFileContent.js | 132 +++++++++--------- .../node/TernNodeDomain.js | 53 +++---- 3 files changed, 97 insertions(+), 110 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index e8ab40bf5f4..74680930d4d 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -405,7 +405,7 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern node when + * Handle the response from the tern node domain when * it responds with the definition * * @param response - the response from the node domain @@ -645,7 +645,7 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern node when + * Handle the response from the tern node domain when * it responds with the list of completions * * @param {{file: string, offset: {line: number, ch: number}, completions:Array., @@ -676,7 +676,7 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern node when + * Handle the response from the tern node domain when * it responds to the get guesses message. * * @param {{file: string, type: string, offset: {line: number, ch: number}, @@ -696,7 +696,7 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern node when + * Handle the response from the tern node domain when * it responds to the update file message. * * @param {{path: string, type: string}} response - the response from node domain @@ -810,7 +810,7 @@ define(function (require, exports, module) { } /** - * Send a message to the tern node - if the module is being initialized, + * Send a message to the tern node domain - if the module is being initialized, * the message will not be posted until initialization is complete */ function postMessage(msg) { @@ -828,7 +828,7 @@ define(function (require, exports, module) { } /** - * Send a message to the tern node - this is only for messages that + * Send a message to the tern node domain - this is only for messages that * need to be sent before and while the addFilesPromise is being resolved. */ function _postMessageByPass(msg) { @@ -859,9 +859,9 @@ define(function (require, exports, module) { } /** - * Handle a request from the tern node for text of a file + * Handle a request from the tern node domain for text of a file * - * @param {{file:string}} request - the request from the tern node. Should be an Object containing the name + * @param {{file:string}} request - the request from the tern node domain. Should be an Object containing the name * of the file tern wants the contents of */ function handleTernGetFile(request) { @@ -878,7 +878,7 @@ define(function (require, exports, module) { /** * Helper function to get the text of a given document and send it to tern. - * If DocumentManager successfully gets the file's text then we'll send it to the tern node. + * If DocumentManager successfully gets the file's text then we'll send it to the tern node domain. * The Promise for getDocumentText() is returned so that custom fail functions can be used. * * @param {string} filePath - the path of the file to get the text of @@ -964,7 +964,7 @@ define(function (require, exports, module) { } /** - * Handle the response from the tern node when + * Handle the response from the tern node domain when * it responds to the prime pump message. * * @param {{path: string, type: string}} response - the response from node domain @@ -1530,7 +1530,7 @@ define(function (require, exports, module) { /** * @private * - * Update the configuration in the tern node. + * Update the configuration in the tern node domain. */ function _setConfig(configUpdate) { config = brackets._configureJSCodeHints.config; diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js index e95ffc5a9a1..5268dbf1803 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -25,76 +25,70 @@ /*jslint node: true */ "use strict"; - -(function () { - var fs = require("fs"), - _dirtyFilesCache = []; - - /** - * Clears the cache for dirty file paths - */ - function clearDirtyFilesCache() { - _dirtyFilesCache = []; - } - - /** - * Updates the files cache with fullpath when dirty flag changes for a document - * If the doc is being marked as dirty then an entry is created in the cache - * If the doc is being marked as clean then the corresponsing entry gets cleared from cache - * - * @param {String} name - fullpath of the document - * @param {boolean} action - whether the document is dirty - */ - function updateDirtyFilesCache(name, action) { - if (action) { - _dirtyFilesCache.push(name); - } else { - var index = _dirtyFilesCache.indexOf(name); - if (index >= 0) { - _dirtyFilesCache.splice(index, 1); - } +var fs = require("fs"), + _dirtyFilesCache = {}; + +/** + * Clears the cache for dirty file paths + */ +function clearDirtyFilesCache() { + _dirtyFilesCache = {}; +} + +/** + * Updates the files cache with fullpath when dirty flag changes for a document + * If the doc is being marked as dirty then an entry is created in the cache + * If the doc is being marked as clean then the corresponsing entry gets cleared from cache + * + * @param {String} name - fullpath of the document + * @param {boolean} action - whether the document is dirty + */ +function updateDirtyFilesCache(name, action) { + if (action) { + _dirtyFilesCache[name] = true; + } else { + if (_dirtyFilesCache[name]) { + delete _dirtyFilesCache[name]; } } - - /** - * Extract content locally from the file system used fs.readFile() - * - * @param {String} fileName - fullpath of the document - * @param {Function} callback - callback handle to post the content back - */ - function _readFile(fileName, callback) { - fs.readFile(fileName, 'utf8', function (err, data) { - var content = ""; - if (!err) { - content = data; - } - console.log("File read ", fileName); - callback.apply(null, [fileName, content]); - }); - } - - /** - * Extracts file content for the given file name(1st param) and invokes the callback handle(2nd param) with - * extracted file content. Content can be extracted locally from the file system used fs.readFile() - * or conditionally from main context(brackets main thread) by using the 3rd param - * - * @param {String} fileName - fullpath of the document - * @param {Function} callback - callback handle to post the content back - * @param {Object} extractFromMainContext - content request handle wrapper from main thread - */ - function extractContent(fileName, callback, extractFromMainContext) { - if (_dirtyFilesCache.indexOf(fileName) !== -1) { - // Ask the main thread context to provide the updated file content - extractFromMainContext.apply(null, [fileName]); - console.log("File read from main thread ", fileName); - } else { - _readFile(fileName, callback); +} + +/** + * Extract content locally from the file system used fs.readFile() + * + * @param {String} fileName - fullpath of the document + * @param {Function} callback - callback handle to post the content back + */ +function _readFile(fileName, callback) { + fs.readFile(fileName, "utf8", function (err, data) { + var content = ""; + if (!err) { + content = data; } + callback.apply(null, [fileName, content]); + }); +} + +/** + * Extracts file content for the given file name(1st param) and invokes the callback handle(2nd param) with + * extracted file content. Content can be extracted locally from the file system used fs.readFile() + * or conditionally from main context(brackets main thread) by using the 3rd param + * + * @param {String} fileName - fullpath of the document + * @param {Function} callback - callback handle to post the content back + * @param {Object} extractFromMainContext - content request handle wrapper from main thread + */ +function extractContent(fileName, callback, extractFromMainContext) { + if (_dirtyFilesCache[fileName]) { + // Ask the main thread context to provide the updated file content + extractFromMainContext.apply(null, [fileName]); + } else { + _readFile(fileName, callback); } - - exports.extractContent = extractContent; - exports.clearFilesCache = clearDirtyFilesCache; - exports.updateFilesCache = updateDirtyFilesCache; - -}()); +} + +exports.extractContent = extractContent; +exports.clearFilesCache = clearDirtyFilesCache; +exports.updateFilesCache = updateDirtyFilesCache; + diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 48d339ef797..d22839c68a1 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -23,6 +23,7 @@ /*eslint-env node */ /*jslint node: true */ +/*global setImmediate */ @@ -37,8 +38,8 @@ var self = { } }; -var Tern = require("./node_modules/tern/lib/tern"), - Infer = require("./node_modules/tern/lib/infer"); +var Tern = require("tern"), + Infer = require("tern/lib/infer"); var ExtractContent = require("./ExtractFileContent"); @@ -234,13 +235,14 @@ function getJumptoDef(fileInfo, offset) { self.postMessage({type: MessageIds.TERN_JUMPTODEF_MSG, file: fileInfo.name, offset: offset}); return; } - var response = {type: MessageIds.TERN_JUMPTODEF_MSG, - file: fileInfo.name, - resultFile: data.file, - offset: offset, - start: data.start, - end: data.end - }; + var response = { + type: MessageIds.TERN_JUMPTODEF_MSG, + file: fileInfo.name, + resultFile: data.file, + offset: offset, + start: data.start, + end: data.end + }; request = buildRequest(fileInfo, "type", offset); // See if we can tell if the reference is to a Function type @@ -283,10 +285,9 @@ function getTernProperties(fileInfo, offset, type) { _log("Error returned from Tern 'properties' request: " + error); } else { //_log("tern properties: completions = " + data.completions.length); - for (i = 0; i < data.completions.length; ++i) { - var property = data.completions[i]; - properties.push({value: property, type: property.type, guess: true}); - } + properties = data.completion.map(function (completion) { + return {value: completion, type: completion.type, guess: true}; + }); } // Post a message back to the main thread with the completions @@ -330,6 +331,10 @@ function getTernHints(fileInfo, offset, isProperty) { completions.push({value: completion.name, type: completion.type, depth: completion.depth, guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}); } + completions = data.completion.map(function (completion) { + return {value: completion.name, type: completion.type, depth: completion.depth, + guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}; + }); } if (completions.length > 0 || !isProperty) { @@ -376,12 +381,7 @@ function getParameters(inferFnType) { function inferArrTypeToString(inferArrType) { var result = "Array.<"; - inferArrType.props[""].types.forEach(function (value, i) { - if (i > 0) { - result += ", "; - } - result += inferTypeToString(value); - }); + result += inferArrType.props[""].types.types.map(inferTypeToString).join(", "); // workaround case where types is zero length if (inferArrType.props[""].types.length === 0) { @@ -403,16 +403,9 @@ function getParameters(inferFnType) { first = true, prop; - for (prop in props) { - if (Object.prototype.hasOwnProperty.call(props, prop)) { - if (!first) { - result += ", "; - } - - first = false; - result += prop + ": " + inferTypeToString(props[prop]); - } - } + result += Object.keys(props).map(function (key) { + return key + ": " + inferTypeToString(props[key]); + }).join(", "); result += "}"; From 4c4514e18d2a125c32eb44dccb5cad7ff990a198 Mon Sep 17 00:00:00 2001 From: Saurabh Kathpalia Date: Wed, 8 Mar 2017 10:43:25 +0530 Subject: [PATCH 20/25] removed redundant for loop(Already added map function for the same) --- .../default/JavaScriptCodeHints/node/TernNodeDomain.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index d22839c68a1..016095993f4 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -326,11 +326,6 @@ function getTernHints(fileInfo, offset, isProperty) { _log("Error returned from Tern 'completions' request: " + error); } else { //_log("found " + data.completions.length + " for " + file + "@" + offset); - for (i = 0; i < data.completions.length; ++i) { - var completion = data.completions[i]; - completions.push({value: completion.name, type: completion.type, depth: completion.depth, - guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}); - } completions = data.completion.map(function (completion) { return {value: completion.name, type: completion.type, depth: completion.depth, guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}; From 93bbe48e5e4d719cf4d877c3c8f302546b25dc99 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Sun, 28 May 2017 20:56:22 +0530 Subject: [PATCH 21/25] Fix for analysis data reset on project change --- .../JavaScriptCodeHints/node/TernNodeDomain.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 016095993f4..7f06dadf513 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -137,12 +137,13 @@ function initTernServer(env, files) { plugins: {requirejs: {}, doc_comment: true, angular: true} }; - // If a server is already created just reset the analysis data + // If a server is already created just reset the analysis data before marking it for GC if (ternServer) { ternServer.reset(); - } else { - ternServer = new Tern.Server(ternOptions); - } + Infer.resetGuessing(); + } + + ternServer = new Tern.Server(ternOptions); files.forEach(function (file) { ternServer.addFile(file); @@ -157,7 +158,6 @@ function resetTernServer() { // If a server is already created just reset the analysis data if (ternServer) { ternServer.reset(); - ternServer.flush(); Infer.resetGuessing(); // tell the main thread we're ready to start processing again self.postMessage({type: MessageIds.TERN_WORKER_READY}); @@ -325,8 +325,8 @@ function getTernHints(fileInfo, offset, isProperty) { if (error) { _log("Error returned from Tern 'completions' request: " + error); } else { - //_log("found " + data.completions.length + " for " + file + "@" + offset); - completions = data.completion.map(function (completion) { + //_log("found " + data.completions + " for " + file + "@" + offset); + completions = data.completions.map(function (completion) { return {value: completion.name, type: completion.type, depth: completion.depth, guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}; }); From a5930aac73848dde0613ac9fd8e7e71cd674df89 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Mon, 29 May 2017 13:02:23 +0530 Subject: [PATCH 22/25] Bug fixes for Untitled document mode change to 'javascript' --- .../default/JavaScriptCodeHints/ScopeManager.js | 12 +----------- .../JavaScriptCodeHints/node/TernNodeDomain.js | 9 ++++----- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index f5520dcd644..5525986a046 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -75,12 +75,6 @@ define(function (require, exports, module) { OFFSET_ZERO = {line: 0, ch: 0}; var config = {}; - - /** - * Used to cache JS Code for hinting in HTML Mixed mode - */ - var cachedJStext = ""; - /** * An array of library names that contain JavaScript builtins definitions. @@ -564,7 +558,7 @@ define(function (require, exports, module) { if (isHtmlFile) { result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, - text: cachedJStext}; + text: session.getJavascriptText()}; } else if (!documentChanges) { result = {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, name: path, @@ -1495,10 +1489,6 @@ define(function (require, exports, module) { currentModule = new TernModule(); } - if (LanguageManager.getLanguageForPath(document.file.fullPath).getId() === "html") { - cachedJStext = session.getJavascriptText(); - } - return currentModule.handleEditorChange(session, document, previousDocument); } diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index 7f06dadf513..b8fd6fb1351 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -208,7 +208,9 @@ function buildRequest(fileInfo, query, offset) { var request = {query: query, files: [], offset: offset, timeout: inferenceTimeout}; if (fileInfo.type !== MessageIds.TERN_FILE_INFO_TYPE_EMPTY) { - request.files.push(fileInfo); + // Create a copy to mutate ahead + var fileInfoCopy = JSON.parse(JSON.stringify(fileInfo)); + request.files.push(fileInfoCopy); } return request; @@ -285,11 +287,10 @@ function getTernProperties(fileInfo, offset, type) { _log("Error returned from Tern 'properties' request: " + error); } else { //_log("tern properties: completions = " + data.completions.length); - properties = data.completion.map(function (completion) { + properties = data.completions.map(function (completion) { return {value: completion, type: completion.type, guess: true}; }); } - // Post a message back to the main thread with the completions self.postMessage({type: type, file: fileInfo.name, @@ -807,8 +808,6 @@ function init(domainManager) { } ] ); - - console.log("Tern Node ready!"); } exports.init = init; From 34c99eeed09e2652ae315fce7fa5cda109afe48f Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Wed, 12 Jul 2017 00:27:48 +0530 Subject: [PATCH 23/25] Remove tern web worker --- .../JavaScriptCodeHints/tern-worker.js | 681 ------------------ 1 file changed, 681 deletions(-) delete mode 100644 src/extensions/default/JavaScriptCodeHints/tern-worker.js diff --git a/src/extensions/default/JavaScriptCodeHints/tern-worker.js b/src/extensions/default/JavaScriptCodeHints/tern-worker.js deleted file mode 100644 index 74c867cb7c6..00000000000 --- a/src/extensions/default/JavaScriptCodeHints/tern-worker.js +++ /dev/null @@ -1,681 +0,0 @@ -/* - * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. - * - * 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. - * - */ - -/*global self, importScripts, require */ - -importScripts("thirdparty/requirejs/require.js"); - -var config = {}; - -(function () { - "use strict"; - - var MessageIds, HintUtils2; - var Tern, Infer; - require(["./MessageIds", "./HintUtils2"], function (messageIds, hintUtils2) { - MessageIds = messageIds; - HintUtils2 = hintUtils2; - var ternRequire = require.config({baseUrl: "./node_modules"}); - ternRequire(["tern/lib/tern", "tern/lib/infer", "tern/plugin/requirejs", "tern/plugin/doc_comment", "tern/plugin/angular"], function (tern, infer, requirejs, docComment) { - Tern = tern; - Infer = infer; - - var ternServer = null, - inferenceTimeout, - isUntitledDoc = false; - - // Save the tern callbacks for when we get the contents of the file - var fileCallBacks = {}; - - /** - * Provide the contents of the requested file to tern - * @param {string} name - the name of the file - * @param {Function} next - the function to call with the text of the file - * once it has been read in. - */ - function getFile(name, next) { - // save the callback - fileCallBacks[name] = next; - - // post a message back to the main thread to get the file contents - self.postMessage({ - type: MessageIds.TERN_GET_FILE_MSG, - file: name - }); - } - - /** - * Send a log message back from the worker to the main thread - * @private - * @param {string} msg - the log message - */ - function _log(msg) { - self.postMessage({log: msg }); - } - - /** - * Report exception - * @private - * @param {Error} e - the error object - */ - function _reportError(e, file) { - if (e instanceof Infer.TimedOut) { - // Post a message back to the main thread with timedout info - self.postMessage({ - type: MessageIds.TERN_INFERENCE_TIMEDOUT, - file: file - }); - } else { - _log("Error thrown in tern_worker:" + e.message + "\n" + e.stack); - } - } - - /** - * Handle a response from the main thread providing the contents of a file - * @param {string} file - the name of the file - * @param {string} text - the contents of the file - */ - function handleGetFile(file, text) { - var next = fileCallBacks[file]; - if (next) { - try { - next(null, text); - } catch (e) { - _reportError(e, file); - } - } - delete fileCallBacks[file]; - } - - function _getNormalizedFilename(fileName) { - if (!isUntitledDoc && ternServer.projectDir && fileName.indexOf(ternServer.projectDir) === -1) { - fileName = ternServer.projectDir + fileName; - } - return fileName; - } - - function _getDenormalizedFilename(fileName) { - if (!isUntitledDoc && ternServer.projectDir && fileName.indexOf(ternServer.projectDir) === 0) { - fileName = fileName.slice(ternServer.projectDir.length); - } - return fileName; - } - - /** - * Create a new tern server. - * - * @param {Object} env - an Object with the environment, as read in from - * the json files in node_modules/tern/defs - * @param {Array.} files - a list of filenames tern should be aware of - */ - function initTernServer(dir, env, files) { - var ternOptions = { - projectDir: dir, - defs: env, - async: true, - getFile: getFile, - plugins: {requirejs: {}, doc_comment: true, angular: true} - }; - ternServer = new Tern.Server(ternOptions); - - files.forEach(function (file) { - ternServer.addFile(file); - }); - - } - - /** - * Create a "empty" update object. - * - * @param {string} path - full path of the file. - * @return {{type: string, name: string, offsetLines: number, text: string}} - - * "empty" update. - - */ - function createEmptyUpdate(path) { - return {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, - name: path, - offsetLines: 0, - text: ""}; - } - - /** - * Build an object that can be used as a request to tern. - * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {string} query - the type of request being made - * @param {{line: number, ch: number}} offset - - */ - function buildRequest(fileInfo, query, offset) { - query = {type: query}; - query.start = offset; - query.end = offset; - query.file = (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) ? "#0" : fileInfo.name; - query.filter = false; - query.sort = false; - query.depths = true; - query.guess = true; - query.origins = true; - query.types = true; - query.expandWordForward = false; - query.lineCharPositions = true; - query.docs = true; - query.urls = true; - - var request = {query: query, files: [], offset: offset, timeout: inferenceTimeout}; - if (fileInfo.type !== MessageIds.TERN_FILE_INFO_TYPE_EMPTY) { - request.files.push(fileInfo); - } - - return request; - } - - /** - * Get definition location - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - the offset into the - * file for cursor - */ - function getJumptoDef(fileInfo, offset) { - var request = buildRequest(fileInfo, "definition", offset); - // request.query.typeOnly = true; // FIXME: tern doesn't work exactly right yet. - - try { - ternServer.request(request, function (error, data) { - if (error) { - _log("Error returned from Tern 'definition' request: " + error); - self.postMessage({type: MessageIds.TERN_JUMPTODEF_MSG, file: fileInfo.name, offset: offset}); - return; - } - - var response = {type: MessageIds.TERN_JUMPTODEF_MSG, - file: _getNormalizedFilename(fileInfo.name), - resultFile: data.file, - offset: offset, - start: data.start, - end: data.end - }; - - request = buildRequest(fileInfo, "type", offset); - // See if we can tell if the reference is to a Function type - ternServer.request(request, function (error, data) { - if (!error) { - response.isFunction = data.type.length > 2 && data.type.substring(0, 2) === "fn"; - } - - // Post a message back to the main thread with the definition - self.postMessage(response); - }); - - }); - } catch (e) { - _reportError(e, fileInfo.name); - } - } - - /** - * Get all the known properties for guessing. - * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - - * the offset into the file where we want completions for - * @param {string} type - the type of the message to reply with. - */ - function getTernProperties(fileInfo, offset, type) { - - var request = buildRequest(fileInfo, "properties", offset), - i; - //_log("tern properties: request " + request.type + dir + " " + file); - try { - ternServer.request(request, function (error, data) { - var properties = []; - if (error) { - _log("Error returned from Tern 'properties' request: " + error); - } else { - //_log("tern properties: completions = " + data.completions.length); - for (i = 0; i < data.completions.length; ++i) { - var property = data.completions[i]; - properties.push({value: property, type: property.type, guess: true}); - } - } - - // Post a message back to the main thread with the completions - self.postMessage({type: type, - file: _getNormalizedFilename(fileInfo.name), - offset: offset, - properties: properties - }); - }); - } catch (e) { - _reportError(e, fileInfo.name); - } - } - - /** - * Get the completions for the given offset - * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - - * the offset into the file where we want completions for - * @param {boolean} isProperty - true if getting a property hint, - * otherwise getting an identifier hint. - */ - function getTernHints(fileInfo, offset, isProperty) { - - var request = buildRequest(fileInfo, "completions", offset), - i; - - //_log("request " + dir + " " + file + " " + offset /*+ " " + text */); - try { - ternServer.request(request, function (error, data) { - var completions = []; - if (error) { - _log("Error returned from Tern 'completions' request: " + error); - } else { - //_log("found " + data.completions.length + " for " + file + "@" + offset); - for (i = 0; i < data.completions.length; ++i) { - var completion = data.completions[i]; - completions.push({value: completion.name, type: completion.type, depth: completion.depth, - guess: completion.guess, origin: completion.origin, doc: completion.doc, url: completion.url}); - } - } - - if (completions.length > 0 || !isProperty) { - // Post a message back to the main thread with the completions - self.postMessage({type: MessageIds.TERN_COMPLETIONS_MSG, - file: _getNormalizedFilename(fileInfo.name), - offset: offset, - completions: completions - }); - } else { - // if there are no completions, then get all the properties - getTernProperties(fileInfo, offset, MessageIds.TERN_COMPLETIONS_MSG); - } - }); - } catch (e) { - _reportError(e, fileInfo.name); - } - } - - /** - * Given a Tern type object, convert it to an array of Objects, where each object describes - * a parameter. - * - * @param {!Infer.Fn} inferFnType - type to convert. - * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. - */ - function getParameters(inferFnType) { - - // work around define functions before use warning. - var recordTypeToString, inferTypeToString, processInferFnTypeParameters, inferFnTypeToString; - - /** - * Convert an infer array type to a string. - * - * Formatted using google closure style. For example: - * - * "Array." - * - * @param {Infer.Arr} inferArrType - * - * @return {string} - array formatted in google closure style. - * - */ - function inferArrTypeToString(inferArrType) { - var result = "Array.<"; - - inferArrType.props[""].types.forEach(function (value, i) { - if (i > 0) { - result += ", "; - } - result += inferTypeToString(value); - }); - - // workaround case where types is zero length - if (inferArrType.props[""].types.length === 0) { - result += "Object"; - } - result += ">"; - - return result; - } - - /** - * Convert properties to a record type annotation. - * - * @param {Object} props - * @return {string} - record type annotation - */ - recordTypeToString = function (props) { - var result = "{", - first = true, - prop; - - for (prop in props) { - if (Object.prototype.hasOwnProperty.call(props, prop)) { - if (!first) { - result += ", "; - } - - first = false; - result += prop + ": " + inferTypeToString(props[prop]); - } - } - - result += "}"; - - return result; - }; - - /** - * Convert an infer type to a string. - * - * @param {*} inferType - one of the Infer's types; Infer.Prim, Infer.Arr, Infer.ANull. Infer.Fn functions are - * not handled here. - * - * @return {string} - * - */ - inferTypeToString = function (inferType) { - var result; - - if (inferType instanceof Infer.AVal) { - inferType = inferType.types[0]; - } - - if (inferType instanceof Infer.Prim) { - result = inferType.toString(); - if (result === "string") { - result = "String"; - } else if (result === "number") { - result = "Number"; - } else if (result === "boolean") { - result = "Boolean"; - } - } else if (inferType instanceof Infer.Arr) { - result = inferArrTypeToString(inferType); - } else if (inferType instanceof Infer.Fn) { - result = inferFnTypeToString(inferType); - } else if (inferType instanceof Infer.Obj) { - if (inferType.name === undefined) { - result = recordTypeToString(inferType.props); - } else { - result = inferType.name; - } - } else { - result = "Object"; - } - - return result; - }; - - /** - * Convert an infer function type to a Google closure type string. - * - * @param {Infer.Fn} inferType - type to convert. - * @return {string} - function type as a string. - */ - inferFnTypeToString = function (inferType) { - var result = "function(", - params = processInferFnTypeParameters(inferType); - - result += HintUtils2.formatParameterHint(params, null, null, true); - if (inferType.retval) { - result += "):"; - result += inferTypeToString(inferType.retval); - } - - return result; - }; - - /** - * Convert an infer function type to string. - * - * @param {*} inferType - one of the Infer's types; Infer.Fn, Infer.Prim, Infer.Arr, Infer.ANull - * @return {Array<{name: string, type: string, isOptional: boolean}>} where each entry in the array is a parameter. - */ - processInferFnTypeParameters = function (inferType) { - var params = [], - i; - - for (i = 0; i < inferType.args.length; i++) { - var param = {}, - name = inferType.argNames[i], - type = inferType.args[i]; - - if (!name) { - name = "param" + (i + 1); - } - - if (name[name.length - 1] === "?") { - name = name.substring(0, name.length - 1); - param.isOptional = true; - } - - param.name = name; - param.type = inferTypeToString(type); - params.push(param); - } - - return params; - }; - - return processInferFnTypeParameters(inferFnType); - } - - /** - * Get the function type for the given offset - * - * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo - * - type of update, name of file, and the text of the update. - * For "full" updates, the whole text of the file is present. For "part" updates, - * the changed portion of the text. For "empty" updates, the file has not been modified - * and the text is empty. - * @param {{line: number, ch: number}} offset - - * the offset into the file where we want completions for - */ - function handleFunctionType(fileInfo, offset) { - var request = buildRequest(fileInfo, "type", offset), - error; - - request.query.preferFunction = true; - - var fnType = ""; - try { - ternServer.request(request, function (ternError, data) { - - if (ternError) { - _log("Error for Tern request: \n" + JSON.stringify(request) + "\n" + ternError); - error = ternError.toString(); - } else { - var file = ternServer.findFile(fileInfo.name); - - // convert query from partial to full offsets - var newOffset = offset; - if (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) { - newOffset = {line: offset.line + fileInfo.offsetLines, ch: offset.ch}; - } - - request = buildRequest(createEmptyUpdate(fileInfo.name), "type", newOffset); - - var expr = Tern.findQueryExpr(file, request.query); - Infer.resetGuessing(); - var type = Infer.expressionType(expr); - type = type.getFunctionType() || type.getType(); - - if (type) { - fnType = getParameters(type); - } else { - ternError = "No parameter type found"; - _log(ternError); - } - } - }); - } catch (e) { - _reportError(e, fileInfo.name); - } - - // Post a message back to the main thread with the completions - self.postMessage({type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG, - file: _getNormalizedFilename(fileInfo.name), - offset: offset, - fnType: fnType, - error: error - }); - } - - /** - * Add an array of files to tern. - * - * @param {Array.} files - each string in the array is the full - * path of a file. - */ - function handleAddFiles(files) { - files.forEach(function (file) { - ternServer.addFile(file); - }); - } - - /** - * Update the context of a file in tern. - * - * @param {string} path - full path of file. - * @param {string} text - content of the file. - */ - function handleUpdateFile(path, text) { - - ternServer.addFile(path, text); - - self.postMessage({type: MessageIds.TERN_UPDATE_FILE_MSG, - path: path - }); - - // reset to get the best hints with the updated file. - ternServer.reset(); - } - - /** - * Make a completions request to tern to force tern to resolve files - * and create a fast first lookup for the user. - * @param {string} path - the path of the file - */ - function handlePrimePump(path) { - var fileName = _getDenormalizedFilename(path); - var fileInfo = createEmptyUpdate(fileName), - request = buildRequest(fileInfo, "completions", {line: 0, ch: 0}); - - try { - ternServer.request(request, function (error, data) { - if (error) { - _log("Error returned from Tern 'completions' request: " + error); - self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG, file: fileInfo.name, path: path}); - return; - } - - // Post a message back to the main thread - self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG, - path: _getNormalizedFilename(path) - }); - }); - } catch (e) { - _reportError(e, path); - } - } - - /** - * Updates the configuration, typically for debugging purposes. - * - * @param {Object} configUpdate new configuration - */ - function setConfig(configUpdate) { - config = configUpdate; - } - - self.addEventListener("message", function (e) { - var file, text, offset, - request = e.data, - type = request.type; - - if (config.debug) { - _log("Message received " + type); - } - - if (type === MessageIds.TERN_INIT_MSG) { - - var dir = request.dir, - env = request.env, - files = request.files; - inferenceTimeout = request.timeout; - - initTernServer(dir, env, files); - } else if (type === MessageIds.TERN_COMPLETIONS_MSG) { - offset = request.offset; - getTernHints(request.fileInfo, offset, request.isProperty); - } else if (type === MessageIds.TERN_GET_FILE_MSG) { - file = request.file; - text = request.text; - handleGetFile(file, text); - } else if (type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { - offset = request.offset; - handleFunctionType(request.fileInfo, offset); - } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { - offset = request.offset; - getJumptoDef(request.fileInfo, offset); - } else if (type === MessageIds.TERN_ADD_FILES_MSG) { - handleAddFiles(request.files); - } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { - isUntitledDoc = request.isUntitledDoc; - handlePrimePump(request.path); - } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { - offset = request.offset; - getTernProperties(request.fileInfo, offset, MessageIds.TERN_GET_GUESSES_MSG); - } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { - handleUpdateFile(request.path, request.text); - } else if (type === MessageIds.SET_CONFIG) { - setConfig(request.config); - } else { - _log("Unknown message: " + JSON.stringify(request)); - } - }); - // tell the main thread we're ready to start processing messages - self.postMessage({type: MessageIds.TERN_WORKER_READY}); - }); - }); - -}()); From 2a3cfcd550fbc02304242e4cee5c64c9c792ce33 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Wed, 12 Jul 2017 22:01:40 +0530 Subject: [PATCH 24/25] Update Acorn and Tern to latest available version --- src/extensions/default/JavaScriptCodeHints/node/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/node/package.json b/src/extensions/default/JavaScriptCodeHints/node/package.json index 6263e368930..fd37424a27b 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/package.json +++ b/src/extensions/default/JavaScriptCodeHints/node/package.json @@ -1,7 +1,7 @@ { "name": "brackets-javascript-code-hints", "dependencies": { - "acorn": "3.3.0", - "tern": "0.20.0" + "acorn": "5.1.1", + "tern": "0.21.0" } } From 0278ae65df9fb02f75978d742380789e0b732265 Mon Sep 17 00:00:00 2001 From: Swagatam Mitra Date: Thu, 13 Jul 2017 19:21:11 +0530 Subject: [PATCH 25/25] Bug fixes - Handle require plugin, untitled doc, doc comment plugin --- .../JavaScriptCodeHints/ScopeManager.js | 4 +-- .../node/ExtractFileContent.js | 9 ++--- .../node/TernNodeDomain.js | 36 +++++++++++++++---- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 5525986a046..ac3eb2c970d 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -1168,7 +1168,7 @@ define(function (require, exports, module) { if (isDocumentDirty && previousDocument) { var updateFilePromise = updateTernFile(previousDocument); updateFilePromise.done(function () { - primePump(path); + primePump(path, document.isUntitled()); addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); } else { @@ -1237,7 +1237,7 @@ define(function (require, exports, module) { addAllFilesAndSubdirectories(projectRoot, function () { // prime the pump again but this time don't wait // for completion. - primePump(path); + primePump(path, false); addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); } else { diff --git a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js index 5268dbf1803..c31d79226b8 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js +++ b/src/extensions/default/JavaScriptCodeHints/node/ExtractFileContent.js @@ -80,12 +80,9 @@ function _readFile(fileName, callback) { * @param {Object} extractFromMainContext - content request handle wrapper from main thread */ function extractContent(fileName, callback, extractFromMainContext) { - if (_dirtyFilesCache[fileName]) { - // Ask the main thread context to provide the updated file content - extractFromMainContext.apply(null, [fileName]); - } else { - _readFile(fileName, callback); - } + // Ask the main thread context to provide the updated file content + // We can't yet use node io to read, to utilize shells encoding detection + extractFromMainContext.apply(null, [fileName]); } exports.extractContent = extractContent; diff --git a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js index b8fd6fb1351..51d6fff416c 100644 --- a/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js +++ b/src/extensions/default/JavaScriptCodeHints/node/TernNodeDomain.js @@ -40,10 +40,16 @@ var self = { var Tern = require("tern"), Infer = require("tern/lib/infer"); + +require("tern/plugin/requirejs"); +require("tern/plugin/doc_comment"); +require("tern/plugin/angular"); + var ExtractContent = require("./ExtractFileContent"); var ternServer = null, + isUntitledDoc = false, inferenceTimeout; // Save the tern callbacks for when we get the contents of the file @@ -92,6 +98,20 @@ function handleGetFile(file, text) { delete fileCallBacks[file]; } +function _getNormalizedFilename(fileName) { + if (!isUntitledDoc && ternServer.projectDir && fileName.indexOf(ternServer.projectDir) === -1) { + fileName = ternServer.projectDir + fileName; + } + return fileName; +} + +function _getDenormalizedFilename(fileName) { + if (!isUntitledDoc && ternServer.projectDir && fileName.indexOf(ternServer.projectDir) === 0) { + fileName = fileName.slice(ternServer.projectDir.length); + } + return fileName; +} + /** * Callback handle to request contents of a file from the main thread * @param {string} file - the name of the file @@ -141,7 +161,7 @@ function initTernServer(env, files) { if (ternServer) { ternServer.reset(); Infer.resetGuessing(); - } + } ternServer = new Tern.Server(ternOptions); @@ -239,7 +259,7 @@ function getJumptoDef(fileInfo, offset) { } var response = { type: MessageIds.TERN_JUMPTODEF_MSG, - file: fileInfo.name, + file: _getNormalizedFilename(fileInfo.name), resultFile: data.file, offset: offset, start: data.start, @@ -293,7 +313,7 @@ function getTernProperties(fileInfo, offset, type) { } // Post a message back to the main thread with the completions self.postMessage({type: type, - file: fileInfo.name, + file: _getNormalizedFilename(fileInfo.name), offset: offset, properties: properties }); @@ -336,7 +356,7 @@ function getTernHints(fileInfo, offset, isProperty) { if (completions.length > 0 || !isProperty) { // Post a message back to the main thread with the completions self.postMessage({type: MessageIds.TERN_COMPLETIONS_MSG, - file: fileInfo.name, + file: _getNormalizedFilename(fileInfo.name), offset: offset, completions: completions }); @@ -630,7 +650,7 @@ function handleFunctionType(fileInfo, offset) { // Post a message back to the main thread with the completions self.postMessage({type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG, - file: fileInfo.name, + file: _getNormalizedFilename(fileInfo.name), offset: offset, fnType: fnType, error: error @@ -674,14 +694,15 @@ function handleUpdateFile(path, text) { * @param {string} path - the path of the file */ function handlePrimePump(path) { - var fileInfo = createEmptyUpdate(path), + var fileName = _getDenormalizedFilename(path); + var fileInfo = createEmptyUpdate(fileName), request = buildRequest(fileInfo, "completions", {line: 0, ch: 0}); try { ternServer.request(request, function (error, data) { // Post a message back to the main thread self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG, - path: path + path: _getNormalizedFilename(path) }); }); } catch (e) { @@ -727,6 +748,7 @@ function _requestTernServer(commandConfig) { } else if (type === MessageIds.TERN_ADD_FILES_MSG) { handleAddFiles(request.files); } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { + isUntitledDoc = request.isUntitledDoc; handlePrimePump(request.path); } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { offset = request.offset;