Skip to content

Commit 821e5a7

Browse files
committed
WIP extracting filesystem and cache functions into create* factories
1 parent 5c6efa4 commit 821e5a7

File tree

1 file changed

+127
-73
lines changed

1 file changed

+127
-73
lines changed

src/index.ts

Lines changed: 127 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ export interface CreateOptions {
329329
* best results.
330330
*/
331331
require?: Array<string>;
332-
readFile?: (path: string) => string | undefined;
333-
fileExists?: (path: string) => boolean;
332+
readFile?: ReadFileFunction;
333+
fileExists?: FileExistsFunction;
334334
transformers?:
335335
| _ts.CustomTransformers
336336
| ((p: _ts.Program) => _ts.CustomTransformers);
@@ -375,6 +375,9 @@ export interface TsConfigOptions
375375
| 'experimentalEsmLoader'
376376
> {}
377377

378+
export type ReadFileFunction = (path: string) => string | undefined;
379+
export type FileExistsFunction = (path: string) => boolean;
380+
378381
/**
379382
* Like `Object.assign`, but ignores `undefined` properties.
380383
*/
@@ -902,18 +905,104 @@ export function create(rawOptions: CreateOptions = {}): Service {
902905
};
903906
}
904907

908+
/**
909+
* Create filesystem access functions usable in `*Host` implementations which
910+
* implement appropriate caching
911+
*/
912+
function createCachedFilesystemFunctions(opts: {
913+
readFile: ReadFileFunction;
914+
fileExists: FileExistsFunction;
915+
}) {
916+
const { readFile: _readFile, fileExists: _fileExists } = opts;
917+
const readFile = cachedLookup(debugFn('readFile', _readFile));
918+
const readDirectory = ts.sys.readDirectory;
919+
const getDirectories = cachedLookup(
920+
debugFn('getDirectories', ts.sys.getDirectories)
921+
);
922+
const fileExists = cachedLookup(debugFn('fileExists', _fileExists));
923+
const directoryExists = cachedLookup(
924+
debugFn('directoryExists', ts.sys.directoryExists)
925+
);
926+
const resolvePath = cachedLookup(
927+
debugFn('resolvePath', ts.sys.resolvePath)
928+
);
929+
const realpath = ts.sys.realpath
930+
? cachedLookup(debugFn('realpath', ts.sys.realpath))
931+
: undefined;
932+
return {
933+
readFile,
934+
readDirectory,
935+
getDirectories,
936+
fileExists,
937+
directoryExists,
938+
resolvePath,
939+
realpath,
940+
};
941+
}
942+
943+
function createUpdateMemoryCacheFunction(opts: {
944+
onProjectMustUpdate: () => void;
945+
isFileKnownToBeInternal: ReturnType<
946+
typeof createResolverFunctions
947+
>['isFileKnownToBeInternal'];
948+
markBucketOfFilenameInternal: ReturnType<
949+
typeof createResolverFunctions
950+
>['markBucketOfFilenameInternal'];
951+
rootFileNames: Set<string>;
952+
fileVersions: Map<string, number>;
953+
fileContents: Map<string, string>;
954+
}) {
955+
const {
956+
onProjectMustUpdate,
957+
isFileKnownToBeInternal,
958+
fileContents,
959+
fileVersions,
960+
markBucketOfFilenameInternal,
961+
rootFileNames,
962+
} = opts;
963+
const updateMemoryCache = (contents: string, fileName: string) => {
964+
let projectMustUpdate = false;
965+
// Add to `rootFiles` as necessary, either to make TS include a file it has not seen,
966+
// or to trigger a re-classification of files from external to internal.
967+
if (!rootFileNames.has(fileName) && !isFileKnownToBeInternal(fileName)) {
968+
markBucketOfFilenameInternal(fileName);
969+
rootFileNames.add(fileName);
970+
projectMustUpdate = true;
971+
}
972+
973+
const previousVersion = fileVersions.get(fileName) || 0;
974+
const previousContents = fileContents.get(fileName);
975+
// Avoid incrementing cache when nothing has changed.
976+
if (contents !== previousContents) {
977+
fileVersions.set(fileName, previousVersion + 1);
978+
fileContents.set(fileName, contents);
979+
projectMustUpdate = true;
980+
}
981+
if (projectMustUpdate) onProjectMustUpdate();
982+
};
983+
return { updateMemoryCache };
984+
}
985+
905986
// Use full language services when the fast option is disabled.
906987
if (!transpileOnly) {
988+
const {
989+
readFile: cachedReadFile,
990+
fileExists: cachedFileExists,
991+
directoryExists,
992+
getDirectories,
993+
readDirectory,
994+
realpath,
995+
resolvePath,
996+
} = createCachedFilesystemFunctions({ readFile, fileExists });
907997
const fileContents = new Map<string, string>();
908998
const rootFileNames = new Set(config.fileNames);
909-
const cachedReadFile = cachedLookup(debugFn('readFile', readFile));
999+
const fileVersions = new Map(
1000+
Array.from(rootFileNames).map((fileName) => [fileName, 0])
1001+
);
9101002

9111003
// Use language services by default (TODO: invert next major version).
9121004
if (!options.compilerHost) {
9131005
let projectVersion = 1;
914-
const fileVersions = new Map(
915-
Array.from(rootFileNames).map((fileName) => [fileName, 0])
916-
);
9171006

9181007
const getCustomTransformers = () => {
9191008
if (typeof transformers === 'function') {
@@ -950,17 +1039,11 @@ export function create(rawOptions: CreateOptions = {}): Service {
9501039
return ts.ScriptSnapshot.fromString(contents);
9511040
},
9521041
readFile: cachedReadFile,
953-
readDirectory: ts.sys.readDirectory,
954-
getDirectories: cachedLookup(
955-
debugFn('getDirectories', ts.sys.getDirectories)
956-
),
957-
fileExists: cachedLookup(debugFn('fileExists', fileExists)),
958-
directoryExists: cachedLookup(
959-
debugFn('directoryExists', ts.sys.directoryExists)
960-
),
961-
realpath: ts.sys.realpath
962-
? cachedLookup(debugFn('realpath', ts.sys.realpath))
963-
: undefined,
1042+
readDirectory,
1043+
getDirectories,
1044+
fileExists: cachedFileExists,
1045+
directoryExists,
1046+
realpath,
9641047
getNewLine: () => ts.sys.newLine,
9651048
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
9661049
getCurrentDirectory: () => cwd,
@@ -985,29 +1068,17 @@ export function create(rawOptions: CreateOptions = {}): Service {
9851068
);
9861069
const service = ts.createLanguageService(serviceHost, registry);
9871070

988-
const updateMemoryCache = (contents: string, fileName: string) => {
989-
// Add to `rootFiles` as necessary, either to make TS include a file it has not seen,
990-
// or to trigger a re-classification of files from external to internal.
991-
if (
992-
!rootFileNames.has(fileName) &&
993-
!isFileKnownToBeInternal(fileName)
994-
) {
995-
markBucketOfFilenameInternal(fileName);
996-
rootFileNames.add(fileName);
997-
// Increment project version for every change to rootFileNames.
998-
projectVersion++;
999-
}
1000-
1001-
const previousVersion = fileVersions.get(fileName) || 0;
1002-
const previousContents = fileContents.get(fileName);
1003-
// Avoid incrementing cache when nothing has changed.
1004-
if (contents !== previousContents) {
1005-
fileVersions.set(fileName, previousVersion + 1);
1006-
fileContents.set(fileName, contents);
1007-
// Increment project version for every file change.
1071+
const { updateMemoryCache } = createUpdateMemoryCacheFunction({
1072+
rootFileNames,
1073+
fileContents,
1074+
fileVersions,
1075+
isFileKnownToBeInternal,
1076+
markBucketOfFilenameInternal,
1077+
onProjectMustUpdate() {
1078+
// Increment project version for every file change or addition to rootFileNames
10081079
projectVersion++;
1009-
}
1010-
};
1080+
},
1081+
});
10111082

10121083
let previousProgram: _ts.Program | undefined = undefined;
10131084

@@ -1070,6 +1141,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
10701141
return { name, comment };
10711142
};
10721143
} else {
1144+
// options.compilerHost === true
10731145
const sys: _ts.System & _ts.FormatDiagnosticsHost = {
10741146
...ts.sys,
10751147
...diagnosticHost,
@@ -1080,18 +1152,12 @@ export function create(rawOptions: CreateOptions = {}): Service {
10801152
if (contents) fileContents.set(fileName, contents);
10811153
return contents;
10821154
},
1083-
readDirectory: ts.sys.readDirectory,
1084-
getDirectories: cachedLookup(
1085-
debugFn('getDirectories', ts.sys.getDirectories)
1086-
),
1087-
fileExists: cachedLookup(debugFn('fileExists', fileExists)),
1088-
directoryExists: cachedLookup(
1089-
debugFn('directoryExists', ts.sys.directoryExists)
1090-
),
1091-
resolvePath: cachedLookup(debugFn('resolvePath', ts.sys.resolvePath)),
1092-
realpath: ts.sys.realpath
1093-
? cachedLookup(debugFn('realpath', ts.sys.realpath))
1094-
: undefined,
1155+
readDirectory,
1156+
getDirectories,
1157+
fileExists: cachedFileExists,
1158+
directoryExists,
1159+
resolvePath,
1160+
realpath,
10951161
};
10961162

10971163
const host: _ts.CompilerHost = ts.createIncrementalCompilerHost
@@ -1147,26 +1213,14 @@ export function create(rawOptions: CreateOptions = {}): Service {
11471213
: transformers;
11481214

11491215
// Set the file contents into cache manually.
1150-
const updateMemoryCache = (contents: string, fileName: string) => {
1151-
const previousContents = fileContents.get(fileName);
1152-
const contentsChanged = previousContents !== contents;
1153-
if (contentsChanged) {
1154-
fileContents.set(fileName, contents);
1155-
}
1156-
1157-
// Add to `rootFiles` when discovered by compiler for the first time.
1158-
let addedToRootFileNames = false;
1159-
if (
1160-
!rootFileNames.has(fileName) &&
1161-
!isFileKnownToBeInternal(fileName)
1162-
) {
1163-
markBucketOfFilenameInternal(fileName);
1164-
rootFileNames.add(fileName);
1165-
addedToRootFileNames = true;
1166-
}
1167-
1168-
// Update program when file changes.
1169-
if (addedToRootFileNames || contentsChanged) {
1216+
const { updateMemoryCache } = createUpdateMemoryCacheFunction({
1217+
rootFileNames,
1218+
fileVersions,
1219+
fileContents,
1220+
isFileKnownToBeInternal,
1221+
markBucketOfFilenameInternal,
1222+
onProjectMustUpdate() {
1223+
// Update program when file changes.
11701224
builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
11711225
Array.from(rootFileNames),
11721226
config.options,
@@ -1175,8 +1229,8 @@ export function create(rawOptions: CreateOptions = {}): Service {
11751229
config.errors,
11761230
config.projectReferences
11771231
);
1178-
}
1179-
};
1232+
},
1233+
});
11801234

11811235
getOutput = (code: string, fileName: string) => {
11821236
const output: [string, string] = ['', ''];

0 commit comments

Comments
 (0)