Persistently cached declaration maps
Follow up to #1767.
In that PR, we computed documentPositionMappers based on declaration map files and cached them at the language service level. This can be rather inefficient because a language service is created per request, so the cached position mappers were recomputed for every LS request.
In this PR, we cache position mappers in the source file structures themselves, and those get persisted across snapshots and therefore across different language services. We achieve this in the following way:
- When we get a request in the server, we get an LS from session that is based on the current snapshot. We use this LS to compute the response.
- We collect all file paths present in the response which could be subjected to declaration mapping.
- We ask session for a new LS that is based on the old snapshot but contains declaration maps for the files collected in the previous step.
- We compute a new response by mapping old positions to new positions using this new LS with declaration map information.
To implement step 3, we now have a new operation on snapshots, CloneWithSourceMaps, which clones a snapshot with additional declaration map information. This is done by creating a snapshotFSBuilder based on the current snapshot, and having the snapshot FS builder read additional files and compute declaration map information.
To persist these newly read files and declaration map information across future snapshots, we also add these files and computed information to the session's current snapshot. This is implemented in CloneWithDiskChanges. We also update file watchers to include any newly read disk file that was added as part of those changes.
Note: a snapshot FS has two kinds of files cached, overlays and disk files. Overlays are files open in the client, and their contents may or may not match the corresponding disk files, if those exist. To simplify the implementation here, we only compute declaration map information based on disk files. That means if a .d.ts file or .d.ts.map file is open in the client, we won't use the overlay contents, and will instead read from disk. If the overlay content matches the disk content, there's no noticeable difference. If it doesn't, there will be a mismatch in the mapped positions. But in this scenario, something has gone wrong already: if the user edits a .d.ts file in the client (but doesn't save it to disk), and that .d.ts had a declaration map generated for it, the mapping of positions is already going to be wrong.