For the longest time, I thought it was a lost cause making my VS Code happy with external Solidity file import (in the way Truffle wants me to). I coped with the red screaming squiggly lines. I learned to ignore and 😒.

Enough is enough.

TLDR

If you are setting up a standard truffle project via `truffle init` or `truffle unbox`, you can use the following settings in VS Code:

"solidity.packageDefaultDependenciesContractsDirectory": "",

"solidity.packageDefaultDependenciesDirectory": "node_modules"

Import from an external package as usual (after installing using npm) like so:

import "openzeppelin-solidity/contracts/math/SafeMath.sol";

Happy Solidity hacking with error-free IDE, autocompletion, and definition lookup.

Why (proper) Import is useful

With the proper setup, Solidity plugin can source autocompletion from all imported files (and their children imports too.) The autocompletion is not yet contextual but gives some information on where the completion element is defined.

Two totalSupply() functions: one from BasicToken.sol and the other from ERC20.sol which BasicToken inherits

With latest version, you can jump to definitions of external imports as well as variables and functions defined in the external packages 🎉.

I contributed the functionality by implementing the corresponding event in LSP (Language Server Protocol) so potentially it can be used in other IDEs (Atom, Sublime, Remix) as well. There are still many features we can implement for Go to Definition and LSP in general in VSCode Solidity. Head over to Github and join the #BUIDL train if you have something on your wish list.

How does Import work in Solidity

Read on for back stories on my resolved frustration and nasty stuff under the rug of Solidity, Truffle, and VSCode Solidity plugin.

It was an ordinary night when I got increasingly irritated 🤬 (again!) by:

me not remembering contract constructor signature I was inheriting from

OpenZeppelin keeping changing contract implementation and interfaces (for good reasons)

VS code Solidity plugin not letting me jump to the file I imported

OpenZeppelin contract being c̶o̶n̶v̶o̶l̶u̶t̶e̶d̶l̶y̶ ̶n̶e̶s̶t̶e̶d̶ categorized in nested folders

and VS code not allowing search in node_modules by default.

I decided to turn my endless mouse clicking and scrolling into more productive detective funzies (insert distracted programmer jokes.) Apparently, this was not an uncommon issue and (shockingly) the Solidity plugin is designed to work with import 🤔.

The answer lies in the two settings mentioned before, packageDefaultDependenciesContractsDirectory and packageDefaultDependenciesDirectory, and the fact that Solidity has no standard on how to reference external package import.

For non-local imports (the one doesn’t start with “.” and isn’t absolute path), the compiler uses its discretion on how to apply the remapping to resolve the import:

When the compiler is invoked, it is not only possible to specify how to discover the first element of a path, but it is possible to specify path prefix remappings so that e.g. github.com/ethereum/dapp-bin/library is remapped to /usr/local/dapp-bin/library and the compiler will read the files from there.

Truffle

Truffle uses truffle-resolver to resolve dependencies from EthPM and NPM. For NPM specifically, it looks for packages under node_modules and replace the prefix with the file system path only.

In addition, Truffle states a Truffle project should use /contracts to store *.sol files and /build(/contracts) for build output. However, the resolver does not enforce this project structure since not all npm Solidity packages follow this layout.

Therefore we import by package name followed with relative path from the root of the npm package. For openzeppelin-solidity, we use

import "openzeppelin-solidity/contracts/math/SafeMath.sol";

VSCode Solidity

VSCode Solidity extension provides two settings with default values to control how external dependencies are resolved:

solidity.packageDefaultDependenciesDirectory: node_modules

node_modules solidity.packageDefaultDependenciesContractsDirectory: contracts

packageDefaultDependenciesDirectory adds a prefix to the package and resolves from the project root. packageDefaultDependenciesContractsDirectory specifies where to find *.sol files inside an external package.

import "package/folder1/contract.sol";

expands to

import "{projectRoot}/{packageDefaultDependenciesDirectory}/package/{packageDefaultDependenciesContractsDirectory}/folder1/contract.sol";

While the default value contracts may look innocent, it actually creates a duplicate in a Truffle project. The previous import would expands to

import "{projectRoot}/node_modules/openzeppelin-solidity/contracts/contracts/math/SafeMath.sol";

Oops. Setting packageDefaultDependenciesContractsDirectory to empty would conform to Truffle standard 🧐.

Bonus Tip🤫

Have you been frustrated when VSCode closes every file that you opened for reference but not editing? This turns out to be a feature (?!) not a bug. You can pin a file to exit preview mode by double clicking the file or tab header. You can also disable preview mode in certain condition or altogether in the setting.

I can understand how this is useful to keep my workspace clean but I found often this feature counter-productive. Would it be better if the file closes itself after some time of inactivity or tuck itself away in the tab list?

Acknowledgment

Last but not least I would like to acknowledge Juan Blanco authoring and all other contributors before me for the amazing VSCode Solidity plugin. Go star the repo to show your love today!