Modules (188)

TernNodeDomain

Description

Dependencies

Functions

Private

_log

Send a log message back from the node to the main thread

msg string
the log message
function _log(msg) {
    console.log(msg);
}
Private

_reportError

Report exception

e Error
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);
    }
}
Private

_requestFileContent

Callback handle to request contents of a file from the main thread

file string
the name of the file
function _requestFileContent(name) {
    self.postMessage({
        type: MessageIds.TERN_GET_FILE_MSG,
        file: name
    });
}

buildRequest

Build an object that can be used as a request to tern.

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
query string
the type of request being made
offset {line: number,ch: number}
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) {
        // Create a copy to mutate ahead
        var fileInfoCopy = JSON.parse(JSON.stringify(fileInfo));
        request.files.push(fileInfoCopy);
    }

    return request;
}

createEmptyUpdate

Create a "empty" update object.

path string
full path of the file.
Returns: {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: ""};
}

formatParameterHint

Format the given parameter array. Handles separators between parameters, syntax for optional parameters, and the order of the parameter type and parameter name.

params non-nullable Array.<{name: string, type: string, isOptional: boolean}>
array of parameter descriptors
appendSeparators optional function(string)
callback function to append separators. The separator is passed to the callback.
appendParameter optional function(string, number)
callback function to append parameter. The formatted parameter type and name is passed to the callback along with the current index of the parameter.
typesOnly optional boolean
only show parameter types. The default behavior is to include both parameter names and types.
Returns: 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;
    }

getFile

Provide the contents of the requested file to tern

name string
the name of the file
next Function
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);
        }
    });
}

getJumptoDef

Get definition location

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
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);
    }
}

getParameters

Given a Tern type object, convert it to an array of Objects, where each object describes a parameter.

inferFnType non-nullable Infer.Fn
type to convert.
Returns: 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;

getRefs

Get all References location

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
the offset into the file for cursor
 function getRefs(fileInfo, offset) {
    var request = buildRequest(fileInfo, "refs", offset);
    try {
        ternServer.request(request, function (error, data) {
            if (error) {
                _log("Error returned from Tern 'refs' request: " + error);
                var response = {
                    type: MessageIds.TERN_REFS,
                    error: error.message
                };
                self.postMessage(response);
                return;
            }
            var response = {
                type: MessageIds.TERN_REFS,
                file: fileInfo.name,
                offset: offset,
                references: data
            };
            // Post a message back to the main thread with the results
            self.postMessage(response);
        });
    } catch (e) {
        _reportError(e, fileInfo.name);
    }
}

getScopeData

Get scope at the offset in the file

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
the offset into the file for cursor
function getScopeData(fileInfo, offset) {
    // Create a new tern Server
    // Existing tern server resolves all the required modules which might take time
    // We only need to analyze single file for getting the scope
    ternOptions.plugins = {};
    var ternServer = new Tern.Server(ternOptions);
    ternServer.addFile(fileInfo.name, fileInfo.text);

    var error;
    var request = buildRequest(fileInfo, "completions", offset); // for primepump

    try {
        // primepump
        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);
                var scope = Infer.scopeAt(file.ast, Tern.resolvePos(file, offset), file.scope);

                if (scope) {
                    // Remove unwanted properties to remove cycles in the object
                    scope = JSON.parse(JSON.stringify(scope, function(key, value) {
                        if (["proto", "propertyOf", "onNewProp", "sourceFile", "maybeProps"].includes(key)) {
                            return undefined;
                        }
                        else if (key === "fnType") {
                             return value.name || "FunctionExpression";
                        }
                        else if (key === "props") {
                            for (var key in value) {
                                value[key] = value[key].propertyName;
                            }
                            return value;
                        } else if (key === "originNode") {
                            return value && {
                                start: value.start,
                                end: value.end,
                                type: value.type,
                                body: {
                                    start: value.body.start,
                                    end: value.body.end
                                }
                            };
                        }

                        return value;
                    }));
                }

                self.postMessage({
                    type: MessageIds.TERN_SCOPEDATA_MSG,
                    file: _getNormalizedFilename(fileInfo.name),
                    offset: offset,
                    scope: scope
                });
            }
        });
    } catch (e) {
        _reportError(e, fileInfo.name);
    } finally {
        ternServer.reset();
        Infer.resetGuessing();
    }
}

getTernHints

Get the completions for the given offset

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
the offset into the file where we want completions for
isProperty boolean
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 + " 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};
                });
            }

            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);
    }
}

getTernProperties

Get all the known properties for guessing.

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
the offset into the file where we want completions for
type string
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);
                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: _getNormalizedFilename(fileInfo.name),
                              offset: offset,
                              properties: properties
                });
        });
    } catch (e) {
        _reportError(e, fileInfo.name);
    }
}

handleAddFiles

Add an array of files to tern.

files Array.<string>
each string in the array is the full path of a file.
function handleAddFiles(files) {
    files.forEach(function (file) {
        ternServer.addFile(file);
    });
}

handleFunctionType

Get the function type for the given offset

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
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
        });
}

handleGetFile

Handle a response from the main thread providing the contents of a file

file string
the name of the file
text string
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;
}

handlePrimePump

Make a completions request to tern to force tern to resolve files and create a fast first lookup for the user.

path string
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) {
            // Post a message back to the main thread
            self.postMessage({type: MessageIds.TERN_PRIME_PUMP_MSG,
                path: _getNormalizedFilename(path)
                });
        });
    } catch (e) {
        _reportError(e, path);
    }
}

handleUpdateFile

Update the context of a file in tern.

path string
full path of file.
text string
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();
    Infer.resetGuessing();
}

inferArrTypeToString

Convert an infer array type to a string.

Formatted using google closure style. For example:

"Array.<string, number>"

inferArrType Infer.Arr
Returns: string
- array formatted in google closure style.
    function inferArrTypeToString(inferArrType) {
        var result = "Array.<";

        result += inferArrType.props["<i>"].types.map(inferTypeToString).join(", ");

        // workaround case where types is zero length
        if (inferArrType.props["<i>"].types.length === 0) {
            result += "Object";
        }
        result += ">";

        return result;
    }
Public API

init

Initialize the test domain with commands and events related to find in files.

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"
            }
        ]
    );
    setTimeout(checkInterfaceAndReInit, 1000);
}

exports.init = init;

initTernServer

Create a new tern server.

env Object
an Object with the environment, as read in from the json files in thirdparty/tern/defs
files Array.<string>
a list of filenames tern should be aware of
function initTernServer(env, files) {
    ternOptions = {
        defs: env,
        async: true,
        getFile: getFile,
        plugins: {requirejs: {}, doc_comment: true, angular: true},
        ecmaVersion: 9
    };

    // If a server is already created just reset the analysis data before marking it for GC
    if (ternServer) {
        ternServer.reset();
        Infer.resetGuessing();
    }
        
    ternServer = new Tern.Server(ternOptions);

    files.forEach(function (file) {
        ternServer.addFile(file);
    });

}

resetTernServer

Resets an existing tern server.

function resetTernServer() {
    // If a server is already created just reset the analysis data 
    if (ternServer) {
        ternServer.reset();
        Infer.resetGuessing();
        // tell the main thread we're ready to start processing again
        self.postMessage({type: MessageIds.TERN_WORKER_READY});
    }
}

setConfig

Updates the configuration, typically for debugging purposes.

configUpdate Object
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);
    }

    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_SCOPEDATA_MSG) {
        offset  = request.offset;
        getScopeData(request.fileInfo, offset);
    } else if (type === MessageIds.TERN_REFS) {
        offset  = request.offset;
        getRefs(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 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 setInterface(msgInterface) {
    MessageIds = msgInterface.messageIds;
}

function checkInterfaceAndReInit() {
    if (!MessageIds) {
        // WTF - Worse than failure
        // We are here as node process got restarted 
        // Request for ReInitialization of interface and Tern Server
        self.postMessage({
            type: "RE_INIT_TERN"
        });
    }
}