Vandelay

VS Code extension for automating imports.

Currently supports JavaScript and Python.

Table of Contents

Overview

Importing code is annoying and the current VS Code tooling around it isn't good enough. This plugin keeps track of all available imports and allows you to quickly import them following whatever style guide your project requires for how import statements get written (see Configuration). Multi-root workspaces are supported (documentation).

Quick Start

Run the Initialize Project command. Profit! Vandelay will suggest imports as you type, or use the Import command to start importing all the things.

The above will be enough for most projects to get off the ground. More complex use-cases may need to further customize the configuration file. See How to Use.

Typescript & Flow Support

Both Typescript and Flow are supported.

Commands

The following commands are available from the Command Palette. Feel free to set your own keyboard shortcuts.

Initialize Project

Run this command to get started with Vandelay. It will create an initial Vandelay configuration file which you can then further customize.

Cache Project Exports

Caches all project exports in all languages that have a Vandelay configuration file (see How to Use). Vandelay will automatically run this command the first time it initializes for a given project, and the plugin will watch for file changes (including git branch switching, file deletion, etc) in order to update its cache of available imports. But you may need to manually run this command if files are changed while VS Code is not running.

Import

Generates an import statement for the selected import from your project.

Import active word

A shortcut to automatically import the word under the cursor. If more than one import matching the active word are found, you'll be asked to choose.

Import undefined variables

Vandelay will attempt to find imports for all undefined variables reported by the linter for the active file. Like the "Import active word" command, if only a single possible import is found for a given variable, it will automatically be imported. Otherwise, you will need to choose the correct one. JavaScript requires the ESLint extension to be installed for this to work, while Python currently supports flake8. If you want undefined Flow types imported, the Flow extension must be installed.

Remove Unused Imports

Vandelay will remove all unused imports reported by the linter. This command requires the linters mentioned above in Import undefined variables.

Fix Imports

Combination of the previous two commands. Imports all undefined variables and removes unused imports.

Fix All Import Paths

Typescript only. For any files that have Typescript diagnostics reported about bad import paths TS2307: Cannot find module '...' , Vandelay will attempt to fix the import paths.

Importing external and environment packages

JavaScript

Vandelay does its best to parse your package.json file (or files, if you're using Yarn workspaces) and gather the available imports from the listed dependencies. Depending on how an NPM package is organized, this sometimes isn't possible. In these cases, however, Vandelay will track your use of package imports and automatically make those available as well. In other words, after manually writing an import statement for a node module, Vandelay will then pick up on that and make it available for automatic imports in the future.

Python

Vandelay tracks the packages you use. This means you'll need to write the import statement yourself the very first time you use something from an external package, but the plugin will remember after that and make it available for automatic importing.

How to Use

Vandelay relies on JavaScript configuration files, not simply JSON, so that the plugin can be fully customized to your project's needs. This means the setup is a bit more complex, but the resulting capability is far more powerful.

Configuration

You must create a file at the root of your project named vandelay-js.js to automate JavaScript imports, or vandelay-py.js to automate Python imports. If using a multi-root workspace, see those instructions. We recommend using the Initialize Project command to create an initial version of this file.

Along with providing configuration options, the presence of this file tells the plugin that it should track your project's imports. The lack of a vandelay-<js|py>.js file in a given project will simply cause the plugin not to run.

The configuration file must be written in JavaScript and export an object ( module.exports = { ... } syntax) containing the desired configuration options. This file may be as simple as something like:

const path = require('path') module.exports = { includePaths: [path.join(__dirname, 'src')], }

The above config will often be sufficient for simple projects, while tools like Prettier make a lot of the below JavaScript options unneccessary. See this sample configuration file for a more complex example.

includePaths: Array<string>

An array of filepaths that Vandelay should watch for exports. This is the only required configuration option.

excludePatterns: Array<string | RegExp>

An array of glob patterns and regular expressions that match filepaths which should be excluded from caching. Vandelay automatically excludes node_modules for JavaScript projects.

importGroups: Array<string> (JS) | Array<Array<string>> (PY)

JavaScript

Vandelay will automatically sort import statements so that node modules come before your project's custom imports, and it will alphabetize them by path. This configuration option allows you to select specific imports that should always be sorted first.

Python

Vandelay will automatically sort import statements so package imports come before your project's custom imports, and it will alphabetize them by path. This configuration option allows you establish some custom ordering, grouping certain imports together with full line breaks if desired. Ungrouped packages will sort before grouped ones, while ungrouped non-package imports will sort after their grouped equivalents. For example:

importGroups: [ ['django', 'rest_framework'], ['src9', 'src1'], ]

The above configuration will result in something like:

import os # ungrouped package import sorts before grouped from django.shortcuts import get_object_or_404 from rest_framework.response import Response import src9 import src1 import src3 # ungrouped non-package import sorts after grouped

maxImportLineLength: number

Defaults to 100. Used to determine when to wrap import statements onto multiple lines.

processImportPath(importPath: string, absImportPath: string, activeFilepath: string, projectRoot: string): ?string

When inserting a new import, this setting allows you to modify the import path that gets written to the file. Useful if you have your build tool configured in a way that allows it to process non-relative paths (for example, all your imports are written relative to the project root). Returning a falsey value will cause the standard relative path to be used.

importPath : _ JavaScript: Relative import path that will be written if you don't return a value _ Python: Dot-style import path that Vandelay is going to write (e.g. django.shortcuts)

: _ Relative import path that will be written if you don't return a value _ Dot-style import path that Vandelay is going to write (e.g. django.shortcuts) absImportPath : Absolute path of the import file

: Absolute path of the import file activeFilepath : Absolute path to the active file open in your editor

: Absolute path to the active file open in your editor projectRoot : Absolute path to the root of your project

JavaScript Example

processImportPath: (importPath, absImportPath, activeFilepath, projectRoot) => absImportPath.startsWith('/Users/eric/my-project/absoluteImportDirectory') ? absImportPath.slice(projectRoot.length + 1) : null

Python Example

processImportPath: (importPath) => importPath.startsWith('my_packages.foo') ? importPath.slice('my_packages.'.length) : importPath

processImportName(importName: string, importPath: string, absImportPath: string, activeFilepath: string, projectRoot: string, isDefault: boolean): ?string

When selecting an import, this setting allows you to modify the names of the imports that will appear in the list and ultimately get written to the file. Returning a falsey value will cause the original import name to be used.

importName : Name of the import.

: Name of the import. importPath : _ JavaScript: Relative import path _ Python: Dot-style import path (e.g. django.shortcuts)

: _ Relative import path _ Dot-style import path (e.g. django.shortcuts) absImportPath : Absolute path of the import file

: Absolute path of the import file activeFilepath : Absolute path to the active file open in your editor

: Absolute path to the active file open in your editor projectRoot : Absolute path to the root of your project

: Absolute path to the root of your project importPath : _ JavaScript: Whether the _ Python: N/A. This argument simply isn't available.

JavaScript Example

processImportName: ( importName, importPath, absImportPath, activeFilepath, projectRoot, isDefault ) => (absImportPath === 'react' ? '* as React' : null)

Python Example

processImportName: (importName) => (importName === 'json' ? 'json as original_json' : null)

shouldIncludeImport(absImportPath: string, activeFilepath: string): boolean

May be used to exclude certain imports from the list of options.

absImportPath : Absolute path of the import file

: Absolute path of the import file activeFilepath : Absolute path to the active file open in your editor

shouldIncludeImport: (absImportPath, activeFilepath) => absImportPath.includes('__mocks__') && !activeFilepath.endsWith('.test.js')

JavaScript Only Options

padCurlyBraces: boolean (JS only)

Defaults to true . Whether import statements should include spaces between curly braces and import names.

useSingleQuotes: boolean (JS only)

Defaults to true . Whether import statements should be writting with single or double quotes.

useSemicolons: boolean (JS only)

Defaults to true . Whether import statements should be writting with semicolons.

cssExtensions: string[] (JS only)

Defaults to ['css', 'pcss'] . Vandelay will offer a CSS Module import option if an adjacent file to the active one exists with the same name and one of the defined CSS extensions. For example, if the same directory contains MyComponent.ts and MyComponent.css , then Vandelay will let you automatically import the CSS Module styles into MyComponent.ts , i.e. import styles from './MyComponent.css'

multilineImportStyle: 'multiple' | 'single' (JS only)

Defaults to multiple . Whether to allow multiple imports on a line when the import needs to span multiple lines because it has gone over the allowed line length.

multiple

import { var1, var2, var3 } from '...'

single

import { var1, var2, var3 } from '...'

trailingComma: boolean (JS only)

Defaults to true . Whether multiline statements should include trailing commas. Only relevant when multilineImportStyle is single .

nonModulePaths: Array<string> (JS Only)

if you have configured your build tool to allow imports relative to the project root for certain paths (thus causing them not to begin with ./ or ../ ), specify the roots of these paths here. These should not be absolute paths, e.g. use src1 not Users/eric/my-project/src1 .

This is done only to prevent them from being considered node_module imports when caching or determining import order. You must use processImportPath to have the desired path actually get written to the file instead of the relative path.

preferTypeOutside: boolean (JS Only)

Defaults to false . If using Flow, settings this to true will cause import statements for types to put the type on the outside of the braces ( import type { Type1, Type2 } ... ) so long as only types are being imported from the given import path. This can help mitigate circular dependency issues under some circumstances. Regardless of this setting, if a value import exists for a given path then the syntax import { myVal, type Type1 } ... will be used.

useES5: boolean (JS only)

Defaults to false . If your project uses ES5 module syntax (i.e. require ) you should set this to true. Only module.exports = { foo, bar } and module.exports = defaultExport syntax is supported.

typescript: boolean (JS only)

Files ending in *.ts will automatically be parsed as Typescript files. You only need to set this configuration option to true if your Typescript files end in *.js .

dependencies: { [nodeModuleName: string]: string } (JS only)

Customize the directory of a node module that Vandelay should look in for that module's imports. Vandleay will search all files and directories inside the one provided to collect all available imports.

dependences: { // For the npm module `@ericbiewener/react`, // look inside that module's `src` directory '@ericbiewener/react': 'src' }

Multi-Root Workspace

You must add a .vandelay directory to your workspace that contains a file named vandelay-<js|py>.js . Along with the above configuration options, you must also provide a projectRoot string that specifies the absolute path to the directory that should be considered the overall root of your project. This will be used for determining relative paths (these paths may always be adjusted via the processImportPath configuration option described above.

Example Configuration File for a JavaScript Project

const path = require('path') const src1 = path.join(__dirname, 'src1') const src2 = path.join(__dirname, 'src2') const src3 = path.join(__dirname, 'src3') module.exports = { includePaths: [src1, src2, src3], excludePatterns: [ // No need to include `node_modules`. Vandelay will exclude that automatically. '**/*.test.*', /.*\/flow-typed\/.*/, /.*\/config\/.*/, ], /** * Webpack configured to use allow imports relative to the project root for anything in `src1`. * - `nonModulePaths` config tells Vandelay that imports beginning with these paths should not be * considered node_modules. * - `processImportPath` config tells Vandelay to write paths for `src1` relative to the project root. */ nonModulePaths: ['src1'], processImportPath: (importPath, absImportPath, activeFilepath, projectRoot) => { if (absImportPath.startsWith(src1)) return absImportPath.slice(projectRoot.length + 1) }, shouldIncludeImport: (absImportPath, activeFilepath) => { return ( !(activeFilepath.startsWith(src2) && absImportPath.startsWith(src3)) && // src2 can't import from src3 !(activeFilepath.startsWith(src3) && absImportPath.startsWith(src1)) && // src3 can't import from src2 // src1 can only import from src1 !( activeFilepath.startsWith(src1) && (absImportPath.startsWith(src2) || absImportPath.startsWith(src3)) ) ) }, }

Example Configuration File for a Python Project

const path = require('path') const src1 = path.join(__dirname, 'src1') const src2 = path.join(__dirname, 'src2') module.exports = { includePaths: [src1, src2, src3], excludePatterns: [ '**/*.test.*', '**/migrations/**/*.*', '**/management/commands/**/*.*', /.*\/config\/.*/, ], importGroups: [ ['django', 'rest_framework'], ['src9', 'src1'], ], maxImportLineLength: 120, processImportPath: (importPath) => importPath.startsWith('my_packages.foo') ? importPath.slice('my_packages.'.length) : importPath, shouldIncludeImport: (absImportPath, activeFilepath) => absImportPath.includes('__fixtures__') && !activeFilepath.endsWith('.test.py'), }

Settings

The following settings are available through the VS Code settings interface:

autoImportSingleResult: boolean

Defaults to true . When the Import active word command is used, the import will be automatically written to the file if only a single result is found.

provideCompletions: boolean