Improvements and New Features for the Motoko VSCode Extension
Hello, everyone! Serokell is pleased to announce that we’ve completed a new grant for the Visual Studio Code extension for Motoko, which brings new features and improvements starting with version 0.21.0. Below are the key contributions that we are proud to share with you.
You can also check our pitch video on YouTube: https://youtu.be/3CSH2bRh-ZI
New features
Signature help
Users should now see hints for function parameters as they are being typed.

Types, documentation, and better kinds for completion items
Completion items will now show the type and documentation, when available. In addition, the kind (the icon to the left of the completion item name) should be more accurate.

Scoped completion items
Completion items now consider the scopes in which variables are defined. In the video below, the in-scope definedInForBlock is displayed, while the off-scope definedInIfBlock is omitted, uncluttering the completion list.

References of record fields
The extension was previously able to find fields defined in actors, classes, and modules, but not objects defined in types. We’ve extended the definitions and references searches so that record types are also considered.

Renames
The extension is now able to rename symbols across a project.

Support for multiple compiler versions
Previously, the version of moc.js (the Motoko compiler compiled to JavaScript) used by the extension was hardcoded, varying based on the extension’s version. Now, the correct version of the compiler will be downloaded and cached based on the mops toolchain, or failing that, from the dfx cache.
In logs, you might see messages such as this:

The compiler should be downloaded to <extension_installation_path>/out/compiler/moc-<version>.js
Miscellaneous improvements and fixes
Structured types for every node
Users of node-motoko may have noticed that, when parsing the Abstract Syntax Tree (AST) with types, only DotE expressions contained the typeRep field, with the structured type AST. After some optimizations in the Motoko compiler, we now serialize the typeRep field for all nodes that can be assigned a type.
-
Here are the results of a benchmark before our changes:
┌─────────────┬──────────┐ │ (index) │ Values │ ├─────────────┼──────────┤ │ Mean (ms) │ 1684.89 │ │ Median (ms) │ 1587.55 │ │ Min (ms) │ 1441.61 │ │ Max (ms) │ 2887.5 │ │ Total (ms) │ 16848.86 │ └─────────────┴──────────┘ -
And after our changes:
┌─────────────┬──────────┐ │ (index) │ Values │ ├─────────────┼──────────┤ │ Mean (ms) │ 1615.52 │ │ Median (ms) │ 1535.63 │ │ Min (ms) │ 1378.18 │ │ Max (ms) │ 2736.59 │ │ Total (ms) │ 16155.19 │ └─────────────┴──────────┘
Which show that even after serializing the structured types for all nodes, there is still a small improvement.
As an example, take the following snippet:
func identity<T>(x : T) : T {
x
};
-
Which produces the following AST:
{ name: 'Prog', args: [ { name: 'LetD', args: [ { name: 'VarP', args: [ { name: 'ID', args: [ 'identity' ], type: [Getter/Setter: '<T>(x : T) -> T'], typeRep: [Getter/Setter] { name: 'Func', args: [ 'Local', 'Returns', { name: 'T', args: [ 'Any' ] }, { name: '', args: [ { name: 'Name', args: [ 'x', { name: 'Var', args: [ 'T', '0' ] } ] } ] }, { name: '', args: [ { name: 'Var', args: [ 'T', '0' ] } ] } ] }, doc: [Getter/Setter: undefined], start: [ 1, 5 ], end: [ 1, 13 ] } ], type: '<T>(x : T) -> T', typeRep: { name: 'Func', args: [ 'Local', 'Returns', { name: 'T', args: [ 'Any' ] }, { name: '', args: [ { name: 'Name', args: [ 'x', { name: 'Var', args: [ 'T', '0' ] } ] } ] }, { name: '', args: [ { name: 'Var', args: [ 'T', '0' ] } ] } ] }, start: [ 1, 5 ], end: [ 1, 13 ] }, { name: 'FuncE', args: [ '<T>(x : T) -> T', 'Local', 'identity', { name: 'T', args: [ { name: 'PrimT', args: [ 'Any' ], type: 'Any', typeRep: 'Any', start: [ 1, 14 ], end: [ 1, 15 ] } ], start: [ 1, 14 ], end: [ 1, 15 ] }, { name: 'ParP', args: [ { name: 'AnnotP', args: [ { name: 'VarP', args: [ { name: 'ID', args: [ 'x' ], type: [Getter/Setter: 'T'], typeRep: [Getter/Setter] { name: 'Con', args: [ 'T' ] }, doc: [Getter/Setter: undefined], start: [ 1, 17 ], end: [ 1, 18 ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 1, 17 ], end: [ 1, 18 ] }, { name: 'PathT', args: [ { name: 'IdH', args: [ { name: 'ID', args: [ 'T' ], type: [Getter/Setter: undefined], typeRep: [Getter/Setter: undefined], doc: [Getter/Setter: undefined], start: [ 1, 21 ], end: [ 1, 22 ] } ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 1, 21 ], end: [ 1, 22 ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 1, 17 ], end: [ 1, 22 ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 1, 16 ], end: [ 1, 23 ] }, { name: 'PathT', args: [ { name: 'IdH', args: [ { name: 'ID', args: [ 'T' ], type: [Getter/Setter: undefined], typeRep: [Getter/Setter: undefined], doc: [Getter/Setter: undefined], start: [ 1, 26 ], end: [ 1, 27 ] } ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 1, 26 ], end: [ 1, 27 ] }, '', { name: 'BlockE', args: [ { name: 'ExpD', args: [ { name: 'VarE', args: [ { name: 'ID', args: [ 'x' ], type: [Getter/Setter: 'T'], typeRep: [Getter/Setter] { name: 'Con', args: [ 'T' ] }, doc: [Getter/Setter: undefined], start: [ 2, 2 ], end: [ 2, 3 ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 2, 2 ], end: [ 2, 3 ] } ], start: [ 2, 2 ], end: [ 2, 3 ] } ], type: 'T', typeRep: { name: 'Con', args: [ 'T' ] }, start: [ 1, 28 ], end: [ 3, 1 ] } ], type: '<T>(x : T) -> T', typeRep: { name: 'Func', args: [ 'Local', 'Returns', { name: 'T', args: [ 'Any' ] }, { name: '', args: [ { name: 'Name', args: [ 'x', { name: 'Var', args: [ 'T', '0' ] } ] } ] }, { name: '', args: [ { name: 'Var', args: [ 'T', '0' ] } ] } ] }, start: [ 1, 0 ], end: [ 3, 1 ] } ], start: [ 1, 0 ], end: [ 3, 1 ] } ] }
The typeRep fields now appear consistently, allowing developers to inspect types.
Tests for all capabilities
One of the aspects that made the extension’s codebase difficult to work with was the lack of tests. Most LSP capabilities had no tests, and during our first grant, Serokell laid the groundwork for writing proper tests, adding them for completions, definitions, and references. Now, for our second grant, we’ve added tests for other capabilities that were untested: code actions (organize imports, quick fix imports), workspace symbols, and document symbols. We are positive that this will help contributors make changes more fearlessly, with better guarantees that their contributions work as intended.
Typer error recovery
The extension should now be more lenient when type-checking Motoko source code, showing more diagnostics related to types and allowing more capabilities to work despite the presence of type errors.

The video above shows that we can see multiple typer errors. We can also hover over a to see that its type is Text.
Increased support for error recovery also improves the UX around two newly implemented features: signature help and support for completions for local and nested modules.
Local and nested modules completions
Previously, the extension would not display completions for modules that were defined locally or within other modules. Take the following snippet, as an example:
module Local {
public let foo : Int = 5;
public let bar : Text = "test";
public type Foo = Nat;
public module Nested {
public let baz : Text = "nested";
};
};
let a = Local.;
let b = Local.Nested.;
Trying to complete from Local. or Local.Nested. will now display the correct completion items.

