First…What is Tern?

Jeff Willette
6 min readDec 2, 2017

--

tern autocompleting

Tern is an awesome Javascript tool, but I find it to be hard to reason about sometimes, which leads me down black holes of discordant config tweaking until it magically works. I recently took a deeper dive into understanding it and wanted to share with you (and my future self when I come back googling)

Tern:

  • An Http server which accepts JSON post requests
  • Usually run in an editor through a plugin (vim, atom, etc.)
  • Reads from a .tern-project file in the project root
  • Upon starting, creates a .tern-port file so your editor knows which port to connect to
  • Reads a series of files from your project into memory and returns type/autocomplete and other useful info when requested
  • See more at http://ternjs.net/doc/manual.html

The problem I usually have with it, is that it seems to sporadically fail to complete [some] things. This can be demonstrated by making a config and manually starting the server with the --verbose option set. In a stockcreate-react-app application, add the following file to the project root

.tern-project

{
"libs": [
"browser"
],
"loadEagerly": [],
"dontLoad": [],
"plugins": {
"modules": {},
"es_modules": {},
"requirejs": {},
"commonjs": {}
}
}

Install and run the binary in another terminal

# if you haven't installed tern already
$ yarn global add tern
$ cd project-dir
$ tern --verbose
# Outputs: Listening on port [port#]

now that you have a server running in the project directory, your editor (which must have a tern plugin http://ternjs.net/doc/manual.html#editor) should pick up on the .tern-port file and know that there is a server running and it doesn’t have to start a new one.

If I open a new file called autocompleteme.js in the src directory and try something that should complete, I see this…

tern autocompleting(?)

So tern is….working? ¯_(ツ)_/¯

Well, kind of…everything is wired up to work, but if you try to access some property of the imported object and you will see no completions.

tern not autocompleting

If you go back to the terminal running the server you should see some requests and responses that came in. (this is a lot of JSON, so just scroll past if you only want an explanation).

Request: {
"query": {
"type": "completions",
"types": true,
"depths": true,
"docs": false,
"filter": true,
"caseInsensitive": true,
"guess": true,
"sort": false,
"expandWordForward": true,
"omitObjectPrototype": false,
"includeKeywords": false,
"inLiteral": true,
"file": "#0",
"end": {
"line": 2,
"ch": 0
},
"lineCharPositions": true
},
"files": [
{
"type": "full",
"name": "src/autocompleteme.js",
"text": "import React, { Component } from \"react\";\n\nRea\n"
}
]
}
Response: {
"start": {
"line": 2,
"ch": 0
},
"end": {
"line": 2,
"ch": 3
},
"isProperty": false,
"isObjectKey": false,
"completions": [
{
"name": "React",
"type": "?",
"depth": 0
}
]
}

You can see that it sends the current file as well as the cursor position and is asking for completion options. Towards the end, it gives one…`React` with type ? which is what we saw in the auto complete box.

If I scroll down in the terminal output I can see the request that came in after I tried to access the object property.

Request: {
"query": {
"type": "completions",
"types": true,
"depths": true,
"docs": false,
"filter": true,
"caseInsensitive": true,
"guess": true,
"sort": false,
"expandWordForward": true,
"omitObjectPrototype": false,
"includeKeywords": false,
"inLiteral": true,
"file": "#0",
"end": {
"line": 2,
"ch": 6
},
"lineCharPositions": true
},
"files": [
{
"type": "full",
"name": "src/autocompleteme.js",
"text": "import React, { Component } from 'react';\n\nReact.\n"
}
]
}
Response: {
"start": {
"line": 2,
"ch": 6
},
"end": {
"line": 2,
"ch": 6
},
"isProperty": true,
"isObjectKey": false,
"completions": []
}

And there are zero completion options…hmmm. Looking into the docs (http://ternjs.net/doc/manual.html#req_files) I can see there is an endpoint for getting the files that the server has analyzed, basically all the files that tern is able to draw completions from. There is no example request, so with a little tinkering I was able to come up with a curl request that was accepted…

# get the port number from the `.tern-port` file
$ curl -X POST http://localhost:57242 -d '{"query": {"type": "files"}}'
# OUTPUT: {"files":[]}

Aha. It hasn’t found any files to draw completions from, so it is only completing from the text found in the current file which is sent in the request from the editor.

By default, local files are loaded into the Tern server when queries are run on them in the editor.http://ternjs.net/doc/manual.html#configuration

This would explain why it sometimes works and sometimes doesn’t, even on the same code…If you had a tern server running and it reads a file, then it will remember it and draw autocompletions from it. So if your editor was open for a long and many files were opened, it might seemingly be completing everything, only to come back later that day without touching the config and everything seems broken.

How to Fix it

The way to fix this is to make sure you have the right lines in the config which can instruct the server where to look for files and load them right when it starts. For my purposes, when writing javascript, I am usually using create-react-app which uses webpack so I will focus on that plugin, but there are others [here](http://ternjs.net/doc/manual.html#plugins)

When using create-react-app , the webpack config is bundled into node_modules , so we have to use the path to that file.

{
"libs": [
"browser"
],
"loadEagerly": [],
"dontLoad": [],
"plugins": {
"modules": {},
"webpack": {
"configPath": "./node_modules/react-scripts/config/webpack.config.dev.js"
}
}
}

Unfortunately, if we try to run the server here, it will crash because of a missing environment variable that would have been created somewhere if the script was run in the normal way…

The only way I could see around this is to create a shell script that sets the variable and calls tern

custom-tern

#!/bin/bash# while debugging and experimenting, add the pipe to a log file
NODE_ENV=dev tern --persistent --verbose | tee /tmp/tern.log

Once I added that to my ~/bin and changed my editor to use my custom-tern as a tern executable, I was able to see completions in all their glory. If we query the files with curl it can be verified that multiple files were loaded into memory upon startup…

tern autocompleting
$ curl -X POST http://localhost:60662 -d '{"query": {"type": "files"}}'{"files":
["src/autocompleteme.js","node_modules/react/index.js","node_modules/react/cjs/react.production.min.js","node_modules/react/cjs/react.development.js","node_modules/object-assign/index.js","node_modules/fbjs/lib/emptyObject.js","node_modules/fbjs/lib/emptyFunction.js","node_modules/fbjs/lib/invariant.js","node_modules/fbjs/lib/warning.js","node_modules/prop-types/checkPropTypes.js","node_modules/prop-types/lib/ReactPropTypesSecret.js"]}

This article is more about the inner workings of tern and not about setting up your editor, but if you want to see my vimrc you can check out my setup

Things To Consider:

Because tern is essentially loading and resolving all of the modules in a webpack config, the startup time may be a little slow, so the first completion you try to make might not show up right away. Give it a few seconds and try it again.

If tern is not behaving as you would expect, try sending it requests for files like the example above and make sure the source file of the property you are trying to complete is in the list of files. Javascript projects have a variety of layouts, so you may need to use different tern plugins in order to get the files to properly load.

Once everything is working you can remove the redirection in the custom-tern script. (If you need to troubleshoot it again be sure to use tee instead of > because redirecting stdout with > seems to break everything)

I think there is a lot of confusion around the way that tern works and why it breaks because most of the work is abstracted into npm packages and editor plugins, but the place where problem troubleshooting needs to occur (the server) is often hidden in some background process. So in order to understand exactly what needs to be fixed, people need to

  1. Manually start a server in a shell
  2. Open another shell or tool and send it custom JSON requests to probe the state of the server
  3. Go back and read the docs, figure out where your config is wrong and try again until you get it just right.

I hope anyone who finds their way to this will have a better understanding of what to do, and hopefully I saved you some time as well! Thanks

--

--