From cbb5826d0ae3bba3eb49f5d92cb04cc3954c4a61 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Fri, 26 May 2023 10:01:48 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Pick=20PR=20#54401=20(Handle=20a?= =?UTF-8?q?lready=20released=20source=20file...)=20into=20release-5.1=20(#?= =?UTF-8?q?54403)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sheetal Nandi Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com> --- src/services/services.ts | 8 +- .../unittests/tsserver/documentRegistry.ts | 39 ++++ ...n-script-info-with-different-scriptKind.js | 203 ++++++++++++++++++ 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/tsserver/documentRegistry/works-when-reusing-orphan-script-info-with-different-scriptKind.js diff --git a/src/services/services.ts b/src/services/services.ts index f0518d0b1ee67..d1ce067ca40a6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1713,9 +1713,13 @@ export function createLanguageService( // calculate this early so it's not undefined if downleveled to a var (or, if emitted // as a const variable without downleveling, doesn't crash). const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); + let releasedScriptKinds: Set | undefined = new Set(); // If the program is already up-to-date, we can reuse it if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasInvalidatedLibResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { + compilerHost = undefined; + parsedCommandLines = undefined; + releasedScriptKinds = undefined; return; } @@ -1738,6 +1742,7 @@ export function createLanguageService( // After this point, the cache needs to be cleared to allow all collected snapshots to be released compilerHost = undefined; parsedCommandLines = undefined; + releasedScriptKinds = undefined; // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above, // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during @@ -1841,12 +1846,13 @@ export function createLanguageService( // We do not support the scenario where a host can modify a registered // file's script kind, i.e. in one project some file is treated as ".ts" // and in another as ".js" - if (scriptKind === oldSourceFile.scriptKind) { + if (scriptKind === oldSourceFile.scriptKind || releasedScriptKinds!.has(oldSourceFile.resolvedPath)) { return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); } else { // Release old source file and fall through to aquire new file with new script kind documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); + releasedScriptKinds!.add(oldSourceFile.resolvedPath); } } diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index f3b679bb7367f..4f53e1dbd0fb0 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -1,8 +1,11 @@ import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, + closeFilesForSession, createLoggerWithInMemoryLogs, createProjectService, + createSession, + openFilesForSession, TestProjectService, } from "../helpers/tsserver"; import { @@ -103,3 +106,39 @@ describe("unittests:: tsserver:: documentRegistry:: document registry in project baselineTsserverLogs("documentRegistry", "Caches the source file if script info is orphan, and orphan script info changes", service); }); }); + +describe("unittests:: tsserver:: documentRegistry:: works when reusing orphan script info with different scriptKind", () => { + it("works when reusing orphan script info with different scriptKind", () => { + const host = createServerHost({}); + const session = createSession(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); + const newText = "exrpot const x = 10;"; + const content = `import x from 'react';\n${newText}`; + openFilesForSession([ + { file: "^/inmemory/model/6", content, scriptKindName: "TSX", projectRootPath: "/users/user/projects/san" }, + { file: "^/inmemory/model/4", content, scriptKindName: "TSX", projectRootPath: "/users/user/projects/san" }, + ], session); + closeFilesForSession(["^/inmemory/model/4"], session); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: "^/inmemory/model/6", + textChanges: [{ + newText, + start: { line: 1, offset: 1 }, + end: { line: 2, offset: newText.length + 1 } // Remove the import so that structure is not reused + }] + }], + openFiles: [ + { + file: "^/inmemory/model/4", + fileContent: newText, + projectRootPath: "/users/user/projects/san", // Add same document with different script kind + scriptKindName: "TS" + }, + ] + } + }); + baselineTsserverLogs("documentRegistry", "works when reusing orphan script info with different scriptKind", session); + }); +}); \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/documentRegistry/works-when-reusing-orphan-script-info-with-different-scriptKind.js b/tests/baselines/reference/tsserver/documentRegistry/works-when-reusing-orphan-script-info-with-different-scriptKind.js new file mode 100644 index 0000000000000..2e16643e431a4 --- /dev/null +++ b/tests/baselines/reference/tsserver/documentRegistry/works-when-reusing-orphan-script-info-with-different-scriptKind.js @@ -0,0 +1,203 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/a/lib/typesMap.json" doesn't exist +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "^/inmemory/model/6", + "projectRootPath": "/users/user/projects/san", + "fileContent": "import x from 'react';\nexrpot const x = 10;", + "scriptKindName": "TSX" + }, + "seq": 1, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: ^/inmemory/model +Info seq [hh:mm:ss:mss] For info: ^/inmemory/model/6 :: No config files found. +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/san/^ 1 undefined Project: /dev/null/inferredProject1* WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/san/^ 1 undefined Project: /dev/null/inferredProject1* WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/san/node_modules 1 undefined Project: /dev/null/inferredProject1* WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/san/node_modules 1 undefined Project: /dev/null/inferredProject1* WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/node_modules 1 undefined Project: /dev/null/inferredProject1* WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/node_modules 1 undefined Project: /dev/null/inferredProject1* WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /dev/null/inferredProject1* WatchType: Missing file +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/san/node_modules/@types 1 undefined Project: /dev/null/inferredProject1* WatchType: Type roots +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/san/node_modules/@types 1 undefined Project: /dev/null/inferredProject1* WatchType: Type roots +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/node_modules/@types 1 undefined Project: /dev/null/inferredProject1* WatchType: Type roots +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/user/projects/node_modules/@types 1 undefined Project: /dev/null/inferredProject1* WatchType: Type roots +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + ^/inmemory/model/6 SVC-1-0 "import x from 'react';\nexrpot const x = 10;" + + + ^/inmemory/model/6 + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: ^/inmemory/model/6 ProjectRootPath: /users/user/projects/san +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +PolledWatches:: +/users/user/projects/san/^: *new* + {"pollingInterval":500} +/users/user/projects/san/node_modules: *new* + {"pollingInterval":500} +/users/user/projects/node_modules: *new* + {"pollingInterval":500} +/a/lib/lib.d.ts: *new* + {"pollingInterval":500} +/users/user/projects/san/node_modules/@types: *new* + {"pollingInterval":500} +/users/user/projects/node_modules/@types: *new* + {"pollingInterval":500} + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "^/inmemory/model/4", + "projectRootPath": "/users/user/projects/san", + "fileContent": "import x from 'react';\nexrpot const x = 10;", + "scriptKindName": "TSX" + }, + "seq": 2, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: ^/inmemory/model +Info seq [hh:mm:ss:mss] For info: ^/inmemory/model/4 :: No config files found. +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 2 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (2) + ^/inmemory/model/6 SVC-1-0 "import x from 'react';\nexrpot const x = 10;" + ^/inmemory/model/4 SVC-1-0 "import x from 'react';\nexrpot const x = 10;" + + + ^/inmemory/model/6 + Root file specified for compilation + ^/inmemory/model/4 + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (2) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: ^/inmemory/model/6 ProjectRootPath: /users/user/projects/san +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] FileName: ^/inmemory/model/4 ProjectRootPath: /users/user/projects/san +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "close", + "arguments": { + "file": "^/inmemory/model/4" + }, + "seq": 3, + "type": "request" + } +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (2) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: ^/inmemory/model/6 ProjectRootPath: /users/user/projects/san +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "updateOpen", + "arguments": { + "changedFiles": [ + { + "fileName": "^/inmemory/model/6", + "textChanges": [ + { + "newText": "exrpot const x = 10;", + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 2, + "offset": 21 + } + } + ] + } + ], + "openFiles": [ + { + "file": "^/inmemory/model/4", + "fileContent": "exrpot const x = 10;", + "projectRootPath": "/users/user/projects/san", + "scriptKindName": "TS" + } + ] + }, + "seq": 4, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: ^/inmemory/model +Info seq [hh:mm:ss:mss] For info: ^/inmemory/model/4 :: No config files found. +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 3 structureChanged: true structureIsReused:: SafeModules Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (2) + ^/inmemory/model/6 SVC-1-1 "exrpot const x = 10;" + ^/inmemory/model/4 SVC-2-0 "exrpot const x = 10;" + + + ^/inmemory/model/6 + Root file specified for compilation + ^/inmemory/model/4 + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (2) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: ^/inmemory/model/6 ProjectRootPath: /users/user/projects/san +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] FileName: ^/inmemory/model/4 ProjectRootPath: /users/user/projects/san +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "response": true, + "responseRequired": true + } +After request