Skip to content

Commit f44dd6f

Browse files
authored
Merge pull request #24206 from Microsoft/documentRegistery
Cache the latest source file from document registery in script info so that we do not have to reparse orphan script info
2 parents bedc110 + e8a0e56 commit f44dd6f

File tree

5 files changed

+167
-15
lines changed

5 files changed

+167
-15
lines changed

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,12 @@ namespace ts.projectSystem {
407407
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path).fileName), expectedFiles.map(file => file.path));
408408
}
409409

410+
function textSpanFromSubstring(str: string, substring: string): TextSpan {
411+
const start = str.indexOf(substring);
412+
Debug.assert(start !== -1);
413+
return createTextSpan(start, substring.length);
414+
}
415+
410416
/**
411417
* Test server cancellation token used to mock host token cancellation requests.
412418
* The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls
@@ -8420,9 +8426,96 @@ new C();`
84208426
});
84218427
});
84228428

8423-
function textSpanFromSubstring(str: string, substring: string): TextSpan {
8424-
const start = str.indexOf(substring);
8425-
Debug.assert(start !== -1);
8426-
return createTextSpan(start, substring.length);
8427-
}
8429+
describe("document registry in project service", () => {
8430+
const projectRootPath = "/user/username/projects/project";
8431+
const importModuleContent = `import {a} from "./module1"`;
8432+
const file: File = {
8433+
path: `${projectRootPath}/index.ts`,
8434+
content: importModuleContent
8435+
};
8436+
const moduleFile: File = {
8437+
path: `${projectRootPath}/module1.d.ts`,
8438+
content: "export const a: number;"
8439+
};
8440+
const configFile: File = {
8441+
path: `${projectRootPath}/tsconfig.json`,
8442+
content: JSON.stringify({ files: ["index.ts"] })
8443+
};
8444+
8445+
function getProject(service: TestProjectService) {
8446+
return service.configuredProjects.get(configFile.path);
8447+
}
8448+
8449+
function checkProject(service: TestProjectService, moduleIsOrphan: boolean) {
8450+
// Update the project
8451+
const project = getProject(service);
8452+
project.getLanguageService();
8453+
checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]);
8454+
const moduleInfo = service.getScriptInfo(moduleFile.path);
8455+
assert.isDefined(moduleInfo);
8456+
assert.equal(moduleInfo.isOrphan(), moduleIsOrphan);
8457+
const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings());
8458+
assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path), [[key, moduleIsOrphan ? undefined : 1]]);
8459+
}
8460+
8461+
function createServiceAndHost() {
8462+
const host = createServerHost([file, moduleFile, libFile, configFile]);
8463+
const service = createProjectService(host);
8464+
service.openClientFile(file.path);
8465+
checkProject(service, /*moduleIsOrphan*/ false);
8466+
return { host, service };
8467+
}
8468+
8469+
function changeFileToNotImportModule(service: TestProjectService) {
8470+
const info = service.getScriptInfo(file.path);
8471+
service.applyChangesToFile(info, [{ span: { start: 0, length: importModuleContent.length }, newText: "" }]);
8472+
checkProject(service, /*moduleIsOrphan*/ true);
8473+
}
8474+
8475+
function changeFileToImportModule(service: TestProjectService) {
8476+
const info = service.getScriptInfo(file.path);
8477+
service.applyChangesToFile(info, [{ span: { start: 0, length: 0 }, newText: importModuleContent }]);
8478+
checkProject(service, /*moduleIsOrphan*/ false);
8479+
}
8480+
8481+
it("Caches the source file if script info is orphan", () => {
8482+
const { service } = createServiceAndHost();
8483+
const project = getProject(service);
8484+
8485+
const moduleInfo = service.getScriptInfo(moduleFile.path);
8486+
const sourceFile = moduleInfo.cacheSourceFile.sourceFile;
8487+
assert.equal(project.getSourceFile(moduleInfo.path), sourceFile);
8488+
8489+
// edit file
8490+
changeFileToNotImportModule(service);
8491+
assert.equal(moduleInfo.cacheSourceFile.sourceFile, sourceFile);
8492+
8493+
// write content back
8494+
changeFileToImportModule(service);
8495+
assert.equal(moduleInfo.cacheSourceFile.sourceFile, sourceFile);
8496+
assert.equal(project.getSourceFile(moduleInfo.path), sourceFile);
8497+
});
8498+
8499+
it("Caches the source file if script info is orphan, and orphan script info changes", () => {
8500+
const { host, service } = createServiceAndHost();
8501+
const project = getProject(service);
8502+
8503+
const moduleInfo = service.getScriptInfo(moduleFile.path);
8504+
const sourceFile = moduleInfo.cacheSourceFile.sourceFile;
8505+
assert.equal(project.getSourceFile(moduleInfo.path), sourceFile);
8506+
8507+
// edit file
8508+
changeFileToNotImportModule(service);
8509+
assert.equal(moduleInfo.cacheSourceFile.sourceFile, sourceFile);
8510+
8511+
const updatedModuleContent = moduleFile.content + "\nexport const b: number;";
8512+
host.writeFile(moduleFile.path, updatedModuleContent);
8513+
8514+
// write content back
8515+
changeFileToImportModule(service);
8516+
assert.notEqual(moduleInfo.cacheSourceFile.sourceFile, sourceFile);
8517+
assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile.sourceFile);
8518+
assert.equal(moduleInfo.cacheSourceFile.sourceFile.text, updatedModuleContent);
8519+
});
8520+
});
84288521
}

src/server/editorServices.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,8 @@ namespace ts.server {
339339
/*@internal*/
340340
readonly typingsCache: TypingsCache;
341341

342-
private readonly documentRegistry: DocumentRegistry;
342+
/*@internal*/
343+
readonly documentRegistry: DocumentRegistry;
343344

344345
/**
345346
* Container of all known scripts
@@ -474,7 +475,7 @@ namespace ts.server {
474475
extraFileExtensions: []
475476
};
476477

477-
this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory);
478+
this.documentRegistry = createDocumentRegistryInternal(this.host.useCaseSensitiveFileNames, this.currentDirectory, this);
478479
const watchLogLevel = this.logger.hasLevel(LogLevel.verbose) ? WatchLogLevel.Verbose :
479480
this.logger.loggingEnabled() ? WatchLogLevel.TriggerOnly : WatchLogLevel.None;
480481
const log: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => this.logger.info(s)) : noop;
@@ -495,6 +496,19 @@ namespace ts.server {
495496
return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory());
496497
}
497498

499+
/*@internal*/
500+
setDocument(key: DocumentRegistryBucketKey, path: Path, sourceFile: SourceFile) {
501+
const info = this.getScriptInfoForPath(path);
502+
Debug.assert(!!info);
503+
info.cacheSourceFile = { key, sourceFile };
504+
}
505+
506+
/*@internal*/
507+
getDocument(key: DocumentRegistryBucketKey, path: Path) {
508+
const info = this.getScriptInfoForPath(path);
509+
return info && info.cacheSourceFile && info.cacheSourceFile.key === key && info.cacheSourceFile.sourceFile;
510+
}
511+
498512
/* @internal */
499513
ensureInferredProjectsUpToDate_TestOnly() {
500514
this.ensureProjectStructuresUptoDate();

src/server/scriptInfo.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ namespace ts.server {
202202
return fileName[0] === "^" || getBaseFileName(fileName)[0] === "^";
203203
}
204204

205+
/*@internal*/
206+
export interface DocumentRegistrySourceFileCache {
207+
key: DocumentRegistryBucketKey;
208+
sourceFile: SourceFile;
209+
}
210+
205211
export class ScriptInfo {
206212
/**
207213
* All projects that include this file
@@ -221,6 +227,9 @@ namespace ts.server {
221227
/** Set to real path if path is different from info.path */
222228
private realpath: Path | undefined;
223229

230+
/*@internal*/
231+
cacheSourceFile: DocumentRegistrySourceFileCache;
232+
224233
constructor(
225234
private readonly host: ServerHost,
226235
readonly fileName: NormalizedPath,

src/services/documentRegistry.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,18 @@ namespace ts {
8787

8888
releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void;
8989

90+
/*@internal*/
91+
getLanguageServiceRefCounts(path: Path): [string, number | undefined][];
92+
9093
reportStats(): string;
9194
}
9295

96+
/*@internal*/
97+
export interface ExternalDocumentCache {
98+
setDocument(key: DocumentRegistryBucketKey, path: Path, sourceFile: SourceFile): void;
99+
getDocument(key: DocumentRegistryBucketKey, path: Path): SourceFile | undefined;
100+
}
101+
93102
export type DocumentRegistryBucketKey = string & { __bucketKey: any };
94103

95104
interface DocumentRegistryEntry {
@@ -99,10 +108,14 @@ namespace ts {
99108
// language services are referencing the file, then the file can be removed from the
100109
// registry.
101110
languageServiceRefCount: number;
102-
owners: string[];
103111
}
104112

105-
export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry {
113+
export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry {
114+
return createDocumentRegistryInternal(useCaseSensitiveFileNames, currentDirectory);
115+
}
116+
117+
/*@internal*/
118+
export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boolean, currentDirectory = "", externalCache?: ExternalDocumentCache): DocumentRegistry {
106119
// Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have
107120
// for those settings.
108121
const buckets = createMap<Map<DocumentRegistryEntry>>();
@@ -123,12 +136,11 @@ namespace ts {
123136
function reportStats() {
124137
const bucketInfoArray = arrayFrom(buckets.keys()).filter(name => name && name.charAt(0) === "_").map(name => {
125138
const entries = buckets.get(name);
126-
const sourceFiles: { name: string; refCount: number; references: string[]; }[] = [];
139+
const sourceFiles: { name: string; refCount: number; }[] = [];
127140
entries.forEach((entry, name) => {
128141
sourceFiles.push({
129142
name,
130-
refCount: entry.languageServiceRefCount,
131-
references: entry.owners.slice(0)
143+
refCount: entry.languageServiceRefCount
132144
});
133145
});
134146
sourceFiles.sort((x, y) => y.refCount - x.refCount);
@@ -173,14 +185,27 @@ namespace ts {
173185
const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ true);
174186
let entry = bucket.get(path);
175187
const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : compilationSettings.target;
188+
if (!entry && externalCache) {
189+
const sourceFile = externalCache.getDocument(key, path);
190+
if (sourceFile) {
191+
Debug.assert(acquiring);
192+
entry = {
193+
sourceFile,
194+
languageServiceRefCount: 0
195+
};
196+
bucket.set(path, entry);
197+
}
198+
}
199+
176200
if (!entry) {
177201
// Have never seen this file with these settings. Create a new source file for it.
178202
const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, /*setNodeParents*/ false, scriptKind);
179-
203+
if (externalCache) {
204+
externalCache.setDocument(key, path, sourceFile);
205+
}
180206
entry = {
181207
sourceFile,
182208
languageServiceRefCount: 1,
183-
owners: []
184209
};
185210
bucket.set(path, entry);
186211
}
@@ -191,6 +216,9 @@ namespace ts {
191216
if (entry.sourceFile.version !== version) {
192217
entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version,
193218
scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot));
219+
if (externalCache) {
220+
externalCache.setDocument(key, path, entry.sourceFile);
221+
}
194222
}
195223

196224
// If we're acquiring, then this is the first time this LS is asking for this document.
@@ -202,6 +230,7 @@ namespace ts {
202230
entry.languageServiceRefCount++;
203231
}
204232
}
233+
Debug.assert(entry.languageServiceRefCount !== 0);
205234

206235
return entry.sourceFile;
207236
}
@@ -225,13 +254,21 @@ namespace ts {
225254
}
226255
}
227256

257+
function getLanguageServiceRefCounts(path: Path) {
258+
return arrayFrom(buckets.entries(), ([key, bucket]): [string, number | undefined] => {
259+
const entry = bucket.get(path);
260+
return [key, entry && entry.languageServiceRefCount];
261+
});
262+
}
263+
228264
return {
229265
acquireDocument,
230266
acquireDocumentWithKey,
231267
updateDocument,
232268
updateDocumentWithKey,
233269
releaseDocument,
234270
releaseDocumentWithKey,
271+
getLanguageServiceRefCounts,
235272
reportStats,
236273
getKeyForCompilationSettings
237274
};

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8134,7 +8134,6 @@ declare namespace ts.server {
81348134
syntaxOnly?: boolean;
81358135
}
81368136
class ProjectService {
8137-
private readonly documentRegistry;
81388137
/**
81398138
* Container of all known scripts
81408139
*/

0 commit comments

Comments
 (0)