Import statement completions by andrewbranch · Pull Request #43149 · microsoft/TypeScript (original) (raw)

Screen capture demo

Kapture 2021-03-12 at 16 21 03

Enables auto-import-style completions on import statements themselves, e.g.

can offer a completion to

import { useState| } from "react";

(The | indicates the position of the cursor both before and after the completion.)

This PR is large because, although the feature appears very similar to existing auto imports, it requires us to resolve module specifiers immediately instead of in a subsequent completion details request. Being able to do that with any reasonable amount of code reuse required quite a bit of untangling. The implementation is explained in some detail in code review comments, but note that a large amount of the PR diff is just functions being shuffled around.

Note also this feature is opt-in via a user preference (includeCompletionsForImportStatements) because it requires corresponding editor changes. So, you won’t be able to test this yourself without building my VS Code PR as well.

Design

Performance

Following measurements assume these dependencies

"dependencies": { "@angular/core": "^10.0.7", "@types/eslint": "^7.2.5", "@types/lodash": "^4.14.165" "@angular/material": "^11.2.5", "@reduxjs/toolkit": "^1.5.0", "@types/node": "^14.14.17", "@types/puppeteer": "^5.4.2", "@types/react": "^17.0.3", "@types/serverless": "^1.78.17", "aggregate-error": "^3.1.0", "antd": "^4.5.1", "aws-sdk": "*", "eslint": "^7.14.0", "lodash": "4.17.15", "mobx": "^5.15.4", "moment": "2.24.0", "puppeteer": "^6.0.0", "react": "16.12.0", "typescript": "^4.3.0-dev.20210322", }

Import statement completions

Stress-tested with aws-sdk installed, which has just a ridiculous number of exports. First draft took several seconds without caches, which was too much, so I decided to require the first character of the identifier to match the first character of the export name before continuing the fuzzy match. So whereas regular auto imports will offer useState when you have just typed state, import statement completions require you to start with u.

After npm rm aws-sdk:

Normal auto imports

The first draft of this PR incurred about a 15% performance penalty on all auto-imports; that has now been reduced to zero or has improved performance in some scenarios. The auto import cache has been split into two pieces that are independently more durable and more reusable than they were combined.

Previously, the process went something like this:

  1. For each module in the program:
  2. Can we come up with a module specifier from the importing file to that module without crossing someone else’s node_modules?
  3. If that module specifier is a bare specifier (e.g. "lodash"), do the package.json files visible to the importing file list that package?
  4. If the answer to both of those are yes, proceed with adding info about that module's exports to a big array, deduplicating re-exports along the way.
  5. Save all that info in a cache.
  6. Iterate the big map of info and pull out the exports whose names match the partial identifier typed so far.

The main problem with this process is that steps (2) and (3) are pretty expensive, and that work might go to waste after the filter in step (6) is applied. The other problem is that they are dependent on the location of the importing file and the contents of any package.jsons visible to that file, which makes us have to invalidate the cache a lot. Too many inputs combined into a single cache means that when we invalidate the cache, a lot of work that isn’t actually invalid has to be thrown away. Now, we cache “what are the exports and re-exports of every module” and “is file A importable from file B, and by what module specifier” separately. So the process becomes more like:

  1. For each module in the program:
  2. Add info about that module’s exports to a big map (not throwing away info about re-exports)
  3. Cache that big map
  4. Iterate the big map of info and pull out the exports whose names match the partial identifier typed so far.
  5. For each name match, see if the module is importable by the importing file (both by module specifier validity and package.json filtering)
  6. Add that module specifier / importability info to a cache.

This means for subsequent auto imports, we have better chances of getting cache hits, and we do less expensive work up front, even with the caches are totally empty.

Triggering auto import completions in a project with

dev.20210322 (ms) This PR (ms)
completionInfo (cold) 259 264
completionEntryDetails 102 74
completionInfo (cached) 68 63
completionEntryDetails 44 35

To-do

Fixes #31658