From 57e397fd7415b903bb255181105ca43b4929257e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 2 Mar 2021 13:57:03 -0800 Subject: [PATCH 1/2] Use hash of source file text as version for the source file --- src/server/editorServices.ts | 10 +---- src/server/scriptInfo.ts | 41 +++++++------------ .../reference/api/tsserverlibrary.d.ts | 12 +----- 3 files changed, 17 insertions(+), 46 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 2689d4e7cadd0..355f6c556fb89 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -655,12 +655,6 @@ namespace ts.server { /*@internal*/ readonly filenameToScriptInfo = new Map(); private readonly scriptInfoInNodeModulesWatchers = new Map(); - /** - * Contains all the deleted script info's version information so that - * it does not reset when creating script info again - * (and could have potentially collided with version where contents mismatch) - */ - private readonly filenameToScriptInfoVersion = new Map(); // Set of all '.js' files ever opened. private readonly allJsFilesForOpenFileTelemetry = new Map(); @@ -1598,7 +1592,6 @@ namespace ts.server { private deleteScriptInfo(info: ScriptInfo) { this.filenameToScriptInfo.delete(info.path); - this.filenameToScriptInfoVersion.set(info.path, info.getVersion()); const realpath = info.getRealpathIfDifferent(); if (realpath) { this.realpathToScriptInfos!.remove(realpath, info); // TODO: GH#18217 @@ -2624,9 +2617,8 @@ namespace ts.server { if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) { return; } - info = new ScriptInfo(this.host, fileName, scriptKind!, !!hasMixedContent, path, this.filenameToScriptInfoVersion.get(path)); // TODO: GH#18217 + info = new ScriptInfo(this.host, fileName, scriptKind!, !!hasMixedContent, path); // TODO: GH#18217 this.filenameToScriptInfo.set(info.path, info); - this.filenameToScriptInfoVersion.delete(info.path); if (!openedByClient) { this.watchClosedScriptInfo(info); } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index fb3928cec9470..144a75f4505ff 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -1,12 +1,7 @@ namespace ts.server { - export interface ScriptInfoVersion { - svc: number; - text: number; - } - /* @internal */ export class TextStorage { - version: ScriptInfoVersion; + version: string | undefined; /** * Generated only on demand (based on edits, or information requested) @@ -46,14 +41,7 @@ namespace ts.server { */ private pendingReloadFromDisk = false; - constructor(private readonly host: ServerHost, private readonly info: ScriptInfo, initialVersion?: ScriptInfoVersion) { - this.version = initialVersion || { svc: 0, text: 0 }; - } - - public getVersion() { - return this.svc - ? `SVC-${this.version.svc}-${this.svc.getSnapshotVersion()}` - : `Text-${this.version.text}`; + constructor(private readonly host: ServerHost, private readonly info: ScriptInfo) { } public hasScriptVersionCache_TestOnly() { @@ -77,16 +65,17 @@ namespace ts.server { public useText(newText?: string) { this.svc = undefined; this.text = newText; + this.version = undefined; this.lineMap = undefined; this.fileSize = undefined; this.resetSourceMapInfo(); - this.version.text++; } public edit(start: number, end: number, newText: string) { this.switchToScriptVersionCache().edit(start, end - start, newText); this.ownFileText = false; this.text = undefined; + this.version = undefined; this.lineMap = undefined; this.fileSize = undefined; this.resetSourceMapInfo(); @@ -142,6 +131,7 @@ namespace ts.server { public delayReloadFromFileIntoText() { this.pendingReloadFromDisk = true; + this.version = undefined; } /** @@ -225,7 +215,6 @@ namespace ts.server { private switchToScriptVersionCache(): ScriptVersionCache { if (!this.svc || this.pendingReloadFromDisk) { this.svc = ScriptVersionCache.fromString(this.getOrLoadText()); - this.version.svc++; } return this.svc; } @@ -334,10 +323,10 @@ namespace ts.server { readonly scriptKind: ScriptKind, public readonly hasMixedContent: boolean, readonly path: Path, - initialVersion?: ScriptInfoVersion) { + ) { this.isDynamic = isDynamicFileName(fileName); - this.textStorage = new TextStorage(host, this, initialVersion); + this.textStorage = new TextStorage(host, this); if (hasMixedContent || this.isDynamic) { this.textStorage.reload(""); this.realpath = this.path; @@ -347,11 +336,6 @@ namespace ts.server { : getScriptKindFromFileName(fileName); } - /*@internal*/ - getVersion() { - return this.textStorage.version; - } - /*@internal*/ getTelemetryFileSize() { return this.textStorage.getTelemetryFileSize(); @@ -559,10 +543,15 @@ namespace ts.server { } } - getLatestVersion(): string { + getLatestVersion() { // Ensure we have updated snapshot to give back latest version - this.textStorage.getSnapshot(); - return this.textStorage.getVersion(); + const snapShot = this.textStorage.getSnapshot(); + if (this.textStorage.version === undefined) { + // Ensure we have updated snapshot to give back latest version + const text = getSnapshotText(snapShot); + this.textStorage.version = this.host.createHash ? this.host.createHash(text) : generateDjb2Hash(text); + } + return this.textStorage.version; } saveTo(fileName: string) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 71c0361b07752..15f1f52eea4b0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9277,10 +9277,6 @@ declare namespace ts.server.protocol { } } declare namespace ts.server { - interface ScriptInfoVersion { - svc: number; - text: number; - } function isDynamicFileName(fileName: NormalizedPath): boolean; class ScriptInfo { private readonly host; @@ -9295,7 +9291,7 @@ declare namespace ts.server { private formatSettings; private preferences; private textStorage; - constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent: boolean, path: Path, initialVersion?: ScriptInfoVersion); + constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent: boolean, path: Path); isScriptOpen(): boolean; open(newText: string): void; close(fileExists?: boolean): void; @@ -9761,12 +9757,6 @@ declare namespace ts.server { } export class ProjectService { private readonly scriptInfoInNodeModulesWatchers; - /** - * Contains all the deleted script info's version information so that - * it does not reset when creating script info again - * (and could have potentially collided with version where contents mismatch) - */ - private readonly filenameToScriptInfoVersion; private readonly allJsFilesForOpenFileTelemetry; /** * maps external project file name to list of config files that were the part of this project From a400816f530371b6b5b59adfc73ecad5177106cb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 11 May 2021 13:20:19 -0700 Subject: [PATCH 2/2] Add way to handle different script kind for the source file to determine if program is upto date --- src/compiler/program.ts | 7 +++++++ src/compiler/watchPublic.ts | 2 +- src/server/project.ts | 3 ++- src/services/services.ts | 2 +- src/testRunner/unittests/reuseProgramStructure.ts | 4 +++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index a50b60a3171b2..41a3f0ecdc7a6 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -653,6 +653,7 @@ namespace ts { rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path, fileName: string) => string | undefined, + getScriptKind: ((path: Path, fileName: string) => ScriptKind) | undefined, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined, @@ -688,6 +689,7 @@ namespace ts { function sourceFileNotUptoDate(sourceFile: SourceFile) { return !sourceFileVersionUptoDate(sourceFile) || + !sourceFileScriptKindUptoDate(sourceFile) || hasInvalidatedResolution(sourceFile.path); } @@ -695,6 +697,11 @@ namespace ts { return sourceFile.version === getSourceVersion(sourceFile.resolvedPath, sourceFile.fileName); } + function sourceFileScriptKindUptoDate(sourceFile: SourceFile) { + return !getScriptKind || + sourceFile.scriptKind === ensureScriptKind(sourceFile.fileName, getScriptKind(sourceFile.resolvedPath, sourceFile.fileName)); + } + function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { return projectReferenceIsEqualTo(oldRef, newRef) && resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 0296e285b4520..0a7db974d1898 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -426,7 +426,7 @@ namespace ts { // All resolutions are invalid if user provided resolutions const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution); - if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { + if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, /*getScriptKind*/ undefined, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { if (hasChangedConfigFileParsingErrors) { builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); hasChangedConfigFileParsingErrors = false; diff --git a/src/server/project.ts b/src/server/project.ts index 60d045ea8ee0b..3360d55242aff 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -412,7 +412,8 @@ namespace ts.server { } getScriptKind(fileName: string) { - const info = this.getOrCreateScriptInfoAndAttachToProject(fileName); + // Don't attach to the project if script kind is asked + const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); return (info && info.scriptKind)!; // TODO: GH#18217 } diff --git a/src/services/services.ts b/src/services/services.ts index 08f6c8897a29d..5a640193affea 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1312,7 +1312,7 @@ namespace ts { }; // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { + if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), host.getScriptKind ? (_path, fileName) => host.getScriptKind!(fileName) : undefined, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { return; } diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 379c8be11cc52..f3b90df1a5cc2 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -913,7 +913,9 @@ namespace ts { ) { return isProgramUptoDate( program, newRootFileNames, newOptions, - path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse, + path => program.getSourceFileByPath(path)!.version, + /*getScfriptKind*/ undefined, + /*fileExists*/ returnFalse, /*hasInvalidatedResolution*/ returnFalse, /*hasChangedAutomaticTypeDirectiveNames*/ undefined, /*getParsedCommandLine*/ returnUndefined,