Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
cspotcode committed Apr 12, 2021
1 parent 94f96cd commit 86d7edf
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 46 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"v8-compile-cache": "^2.2.0",
"yn": "3.1.1"
},
"prettier": {
Expand Down
104 changes: 58 additions & 46 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { relative, basename, extname, resolve, dirname, join } from 'path';
import { Module } from 'module';
import { relative, basename, extname, resolve, dirname, join, delimiter as pathDelimiter } from 'path';
import * as util from 'util';
import { fileURLToPath } from 'url';

import * as ynModule from 'yn';
import sourceMapSupport = require('source-map-support');
import { BaseError } from 'make-error';
import type * as _ts from 'typescript';
Expand Down Expand Up @@ -374,21 +375,6 @@ export interface Service {
*/
export type Register = Service;

/**
* Cached fs operation wrapper.
*/
function cachedLookup<T>(fn: (arg: string) => T): (arg: string) => T {
const cache = new Map<string, T>();

return (arg: string): T => {
if (!cache.has(arg)) {
cache.set(arg, fn(arg));
}

return cache.get(arg)!;
};
}

/** @internal */
export function getExtensions(config: _ts.ParsedCommandLine) {
const tsExtensions = ['.ts'];
Expand Down Expand Up @@ -779,29 +765,42 @@ export function create(rawOptions: CreateOptions = {}): Service {
}

/**
* Create filesystem access functions usable in `*Host` implementations which
* implement appropriate caching
* Create filesystem access functions which implement appropriate caching and
* are usable in `*Host` implementations.
*/
function createCachedFilesystemFunctions(opts: {
function createCachedFilesystem(opts: {
readFile: ReadFileFunction;
fileExists: FileExistsFunction;
}) {
const { readFile: _readFile, fileExists: _fileExists } = opts;
const readFile = cachedLookup(debugFn('readFile', _readFile));
const readDirectory = ts.sys.readDirectory;

const fileExistsCache = new Map<string, boolean>();
const fileExists = cachedLookup(debugFn('fileExists', _fileExists), fileExistsCache);
const readFileCache = new Map<string, string | undefined>();
const readFile = cachedLookup(debugFn('readFile', _readFile), readFileCache);
function setFileContents(path: string, content: string | undefined) {
readFileCache.set(path, content);
fileExistsCache.set(path, true);
}
// Not cached until TS exposes a proper way to inject fs dependencies so that
// this function obeys our readFile, etc caches.
const readDirectory = (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] => {
debug('readDirectory', path, extensions, exclude, include, depth);
return ts.sys.readDirectory(path, extensions, exclude, include, depth);
}
const getDirectories = cachedLookup(
debugFn('getDirectories', ts.sys.getDirectories)
);
const fileExists = cachedLookup(debugFn('fileExists', _fileExists));
const directoryExists = cachedLookup(
debugFn('directoryExists', ts.sys.directoryExists)
);
const resolvePath = cachedLookup(
debugFn('resolvePath', ts.sys.resolvePath)
);
// Cache probably has little effect; this is an in-memory transformation based on CWD
const resolvePath = debugFn('resolvePath', ts.sys.resolvePath);
// Note: cache does not understand when intermediate symlinks are changed; cannot be invalidated correctly.
const realpath = ts.sys.realpath
? cachedLookup(debugFn('realpath', ts.sys.realpath))
: undefined;

return {
readFile,
readDirectory,
Expand All @@ -810,7 +809,19 @@ export function create(rawOptions: CreateOptions = {}): Service {
directoryExists,
resolvePath,
realpath,
setFileContents,
fileContents: readFileCache,
};

function cachedLookup<T>(fn: (arg: string) => T, cache = new Map<string, T>()): (arg: string) => T {
return (arg: string): T => {
if (!cache.has(arg)) {
cache.set(arg, fn(arg));
}

return cache.get(arg)!;
};
}
}

function createUpdateMemoryCacheFunction(opts: {
Expand All @@ -823,12 +834,14 @@ export function create(rawOptions: CreateOptions = {}): Service {
>['markBucketOfFilenameInternal'];
rootFileNames: Set<string>;
fileVersions: Map<string, number>;
fileContents: Map<string, string>;
fileContents: Map<string, string | undefined>;
setFileContents(path: string, contents: string | undefined): void;
}) {
const {
onProjectMustUpdate,
isFileKnownToBeInternal,
fileContents,
setFileContents,
fileVersions,
markBucketOfFilenameInternal,
rootFileNames,
Expand All @@ -848,7 +861,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
// Avoid incrementing cache when nothing has changed.
if (contents !== previousContents) {
fileVersions.set(fileName, previousVersion + 1);
fileContents.set(fileName, contents);
setFileContents(fileName, contents);
projectMustUpdate = true;
}
if (projectMustUpdate) onProjectMustUpdate();
Expand All @@ -866,8 +879,9 @@ export function create(rawOptions: CreateOptions = {}): Service {
readDirectory,
realpath,
resolvePath,
} = createCachedFilesystemFunctions({ readFile, fileExists });
const fileContents = new Map<string, string>();
setFileContents,
fileContents,
} = createCachedFilesystem({ readFile, fileExists });
const rootFileNames = new Set(config.fileNames);
const fileVersions = new Map(
Array.from(rootFileNames).map((fileName) => [fileName, 0])
Expand All @@ -891,26 +905,22 @@ export function create(rawOptions: CreateOptions = {}): Service {
Required<Pick<_ts.LanguageServiceHost, 'fileExists' | 'readFile'>> = {
getProjectVersion: () => String(projectVersion),
getScriptFileNames: () => Array.from(rootFileNames),
getScriptVersion: (fileName: string) => {
const version = fileVersions.get(fileName);
return version ? version.toString() : '';
},
// Language service calls getScriptSnapshot, then getScriptVersion, in that order
getScriptSnapshot(fileName: string) {
// TODO ordering of this with getScriptVersion? Should they sync up?
let contents = fileContents.get(fileName);

// Read contents into TypeScript memory cache.
if (contents === undefined) {
contents = cachedReadFile(fileName);
if (contents === undefined) return;

fileVersions.set(fileName, 1);
fileContents.set(fileName, contents);
projectVersion++;
}

debug('getScriptSnapshot', fileName);
const contents = cachedReadFile(fileName);
if (contents === undefined) return;
return ts.ScriptSnapshot.fromString(contents);
},
getScriptVersion(fileName: string) {
debug('getScriptVersion', fileName);
let version = fileVersions.get(fileName);
if(version === undefined) {
version = 1;
fileVersions.set(fileName, version);
}
return version.toString();
},
readFile: cachedReadFile,
readDirectory,
getDirectories,
Expand Down Expand Up @@ -944,6 +954,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
const { updateMemoryCache } = createUpdateMemoryCacheFunction({
rootFileNames,
fileContents,
setFileContents,
fileVersions,
isFileKnownToBeInternal,
markBucketOfFilenameInternal,
Expand Down Expand Up @@ -1090,6 +1101,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
rootFileNames,
fileVersions,
fileContents,
setFileContents,
isFileKnownToBeInternal,
markBucketOfFilenameInternal,
onProjectMustUpdate() {
Expand Down

0 comments on commit 86d7edf

Please sign in to comment.