Building the Dataform VS Code extension | Dataform
Guide

Building the Dataform VS Code extension

How we made our own extension for Visual Studio Code.

Engineering
picture of author

George McGowan on September 22, 2020

Currently, our site contains the only IDE that helps you to write SQLX code, offering syntax highlighting and automatic code completion. We wanted to make sure that our open source community benefits from the same tools, and so today we're announcing the release of a SQLX extension for Visual Studio Code.

Features

  • SQLX file syntax highlighting
  • Project compilation on save
  • Cmd + click inside a ref function to view the referenced file

Installation:

  • Install the Dataform CLI locally: npm i -g @dataform/cli
  • Download our extension here
  • Open a project containing a SQLX file
  • You'll see project compilation notifications each time you save a file

Our process:

(If you’re not interested in the details of how I developed the extension, then there's no need to read further. Otherwise, read on!)

I decided to make this extension as part of a hackweek project, during which we dedicate a week towards something different from our normal day jobs. In reality, this ended up taking two hackweeks to be ready for launch. Here is the final source code.

Highlighting:

Initially I just wanted to get syntax highlighting working. Fortunately we already had highlighting that worked out of the box, because we use the Monaco editor in our website IDE. So the main thing was to try to get an extension working in VS Code. For this, I largely followed the docs, which were pretty comprehensive.

One difference between us and the docs is that we use the Bazel build system instead of the standard webpack build. This just meant that I had to include some extra boilerplate to compile our typescript into javascript for the extension.

I simply created a folder containing the recommended start files (below) with some boilerplate code in extension.ts and ran code --extensionDevelopmentPath: ./vscode once I’d compiled my .ts files.

This got me the syntax highlighting straight away!

Compiling:

Later, I realised that we could go a little further than simple syntax highlighting. In our web IDE, we compile the code you write on the fly and show you any errors in real-time. And in the context of the extension, we have access to your whole project. So we should be able to compile the whole thing in the same way.

However, this would require a more complicated VS Code extension. As I understand it, extensions have a Client and a Server architecture. At the time, I was just using the Client, which is fine for simple things like syntax highlighting. However, if I wanted to do things like interface with the user’s file system or suggest definitions, then I would need to use a Language Server. The server and client would communicate with each other by subscribing to events such as onRequest(“compile). So I took some more boilerplate, added it to my server.ts, and managed to get some messages being sent between the client and server.

Then I was ready to implement the project compilation. There were two options I could’ve pursued. The first would be to rebuild a lot of what we already have inside our website backend and run the compilation step inside the language server. The second would be to run our open source CLI inside the language server as a simple terminal process, and save the result of that.

I chose the second option, as it was much easier to get started and I wouldn’t have to rebuild so much code. Fortunately, we already had code very similar to this elsewhere, so I was able to copy leverage much of the code from there. It ended up looking something like this:

  connection.onRequest("compile", async () => {
    const compileResult = await getProcessResult(exec("dataform compile --json"));
    if (compileResult?.compilationError) {
      connection.sendNotification("error", compilationError.message);
    } else {
      connection.sendNotification("success", "Project compiled successfully");
    }
  }

Definitions:

Once this was done, and I had compilation notifications popping up using VS Code’s notifications API, I realised that I could go further yet! Given the contents of a ref function, and a compiled project, I should be able to determine the file name that the ref was referring to.

This required me using the Definitions API, which essentially requires you to create an onDefinition handler in your server, and return a Definition object if the piece of code being requested has a valid definition. Once I ironed out a few bugs, the code looked roughly like this:

  connection.onDefinition(
    (params): HandlerResult<Location, void> => {
      const linkedFileName = retrieveLinkedFileName(params.textDocument.uri);
      const fileString = `${WORKSPACE_ROOT_FOLDER}/${linkedFileName}`;
      return {
        uri: fileString,
        range: {
          start: { line: 0, character: 0 },
          end: { line: 1, character: 0 }
        }
      } as Location;
    }
  );

Publishing

Now I had syntax highlighting, compilation and jump-to-definition working and I decided to publish the first draft of the extension. Mostly this was as described in the docs. I used the vsce package to properly compile a .vsix file ready to be used. There was a bit of work to get the bazel build putting the compiled code in the correct place, but once that was worked out I was able to install the package itself in my VS code extension library and verify that it worked.

The final step was creating an account in Azure, and uploading our extension to the VS Code extension library. I was pleased to see that the vsce package command nicely cuts some details out of the package.json and adds it to the extension library site:

And as a bonus, VS Code automatically detects that .SQLX files have an extension now, and offers to show off our extension!

Conclusion

Hopefully I’ve given you a taste for VS Code extension development. If you’re interested in trying your own extension, I’d highly recommend it as being easy to get started and easy to distribute your code.

This extension is still in Alpha, and no doubt there will be various bugs. Please let us know if you find any and let us know what feature you’d like to see next!

More content from Dataform

What can data teams learn from Google’s State of DevOps report? illustration
Opinion

What can data teams learn from Google’s State of DevOps report?

Google's research shows us that elite engineering teams have a lot in common when it comes to DevOps. We think similar principles apply to data teams or, more precisely, DataOps.
Learn more
Cut data warehouse costs with run caching illustration
Product Update

Cut data warehouse costs with run caching

How to save time and money by using our run caching feature
Learn more
The startup data stack starter pack (2020) illustration
Opinion

The startup data stack starter pack (2020)

Data collection, integration, warehousing, modeling and visualization. What are the options, best of breed products and how much do they all cost.
Learn more

Learn more about the Dataform data modeling platform

Dataform brings open source tooling, best practices, and software engineering inspired workflows to advanced data teams that are seeking to scale, enabling you to deliver reliable data to your entire organization.