Skip to content

Commit

Permalink
Handle already released source files that dont match scriptKind
Browse files Browse the repository at this point in the history
Fixes #54381
  • Loading branch information
sheetalkamat committed May 25, 2023
1 parent 1a8c7ae commit 6f6f29a
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Path> | 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;
}

Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}

Expand Down
39 changes: 39 additions & 0 deletions src/testRunner/unittests/tsserver/documentRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as ts from "../../_namespaces/ts";
import {
baselineTsserverLogs,
closeFilesForSession,
createLoggerWithInMemoryLogs,
createProjectService,
createSession,
openFilesForSession,
TestProjectService,
} from "../helpers/tsserver";
import {
Expand Down Expand Up @@ -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 content = "import React, { useState } from 'react';\n\nconst MyComponent = () => {\n const [numbers, setNumbers] = useState<number[]>([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);\n\n const handleSortAscending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n setNumbers(sortedNumbers);\n };\n\n const handleSortDescending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => b - a);\n setNumbers(sortedNumbers);\n };\n\n return (\n <div>\n <button onClick={handleSortAscending}>Sort Ascending</button>\n <button onClick={handleSortDescending}>Sort Descending</button>\n <ul>\n {numbers.map((number, index) => (\n <li key={index}>{number}</li>\n ))}\n </ul>\n </div>\n );\n};\n\nexport default MyComponent;";
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);
const newText = "const numbers: number[] = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];\nconst sortedNumbers: number[] = numbers.sort((a, b) => a - b);\nconsole.log(sortedNumbers);";
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName: "^/inmemory/model/6",
textChanges: [{
newText,
start: { line: 1, offset: 1 },
end: { line: 29, offset: 28 }
}]
}],
openFiles: [
{
file: "^/inmemory/model/4",
fileContent: newText,
projectRootPath: "/users/user/projects/san",
scriptKindName: "TS"
},
]
}
});
baselineTsserverLogs("documentRegistry", "works when reusing orphan script info with different scriptKind", session);
});
});
Original file line number Diff line number Diff line change
@@ -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 React, { useState } from 'react';\n\nconst MyComponent = () => {\n const [numbers, setNumbers] = useState<number[]>([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);\n\n const handleSortAscending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n setNumbers(sortedNumbers);\n };\n\n const handleSortDescending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => b - a);\n setNumbers(sortedNumbers);\n };\n\n return (\n <div>\n <button onClick={handleSortAscending}>Sort Ascending</button>\n <button onClick={handleSortDescending}>Sort Descending</button>\n <ul>\n {numbers.map((number, index) => (\n <li key={index}>{number}</li>\n ))}\n </ul>\n </div>\n );\n};\n\nexport default MyComponent;",
"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 React, { useState } from 'react';\n\nconst MyComponent = () => {\n const [numbers, setNumbers] = useState<number[]>([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);\n\n const handleSortAscending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n setNumbers(sortedNumbers);\n };\n\n const handleSortDescending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => b - a);\n setNumbers(sortedNumbers);\n };\n\n return (\n <div>\n <button onClick={handleSortAscending}>Sort Ascending</button>\n <button onClick={handleSortDescending}>Sort Descending</button>\n <ul>\n {numbers.map((number, index) => (\n <li key={index}>{number}</li>\n ))}\n </ul>\n </div>\n );\n};\n\nexport default MyComponent;"
^/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::
/a/lib/lib.d.ts: *new*
{"pollingInterval":500}
/users/user/projects/node_modules: *new*
{"pollingInterval":500}
/users/user/projects/node_modules/@types: *new*
{"pollingInterval":500}
/users/user/projects/san/^: *new*
{"pollingInterval":500}
/users/user/projects/san/node_modules: *new*
{"pollingInterval":500}
/users/user/projects/san/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 React, { useState } from 'react';\n\nconst MyComponent = () => {\n const [numbers, setNumbers] = useState<number[]>([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);\n\n const handleSortAscending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n setNumbers(sortedNumbers);\n };\n\n const handleSortDescending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => b - a);\n setNumbers(sortedNumbers);\n };\n\n return (\n <div>\n <button onClick={handleSortAscending}>Sort Ascending</button>\n <button onClick={handleSortDescending}>Sort Descending</button>\n <ul>\n {numbers.map((number, index) => (\n <li key={index}>{number}</li>\n ))}\n </ul>\n </div>\n );\n};\n\nexport default MyComponent;",
"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 React, { useState } from 'react';\n\nconst MyComponent = () => {\n const [numbers, setNumbers] = useState<number[]>([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);\n\n const handleSortAscending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n setNumbers(sortedNumbers);\n };\n\n const handleSortDescending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => b - a);\n setNumbers(sortedNumbers);\n };\n\n return (\n <div>\n <button onClick={handleSortAscending}>Sort Ascending</button>\n <button onClick={handleSortDescending}>Sort Descending</button>\n <ul>\n {numbers.map((number, index) => (\n <li key={index}>{number}</li>\n ))}\n </ul>\n </div>\n );\n};\n\nexport default MyComponent;"
^/inmemory/model/4 SVC-1-0 "import React, { useState } from 'react';\n\nconst MyComponent = () => {\n const [numbers, setNumbers] = useState<number[]>([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);\n\n const handleSortAscending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n setNumbers(sortedNumbers);\n };\n\n const handleSortDescending = () => {\n const sortedNumbers = [...numbers].sort((a, b) => b - a);\n setNumbers(sortedNumbers);\n };\n\n return (\n <div>\n <button onClick={handleSortAscending}>Sort Ascending</button>\n <button onClick={handleSortDescending}>Sort Descending</button>\n <ul>\n {numbers.map((number, index) => (\n <li key={index}>{number}</li>\n ))}\n </ul>\n </div>\n );\n};\n\nexport default MyComponent;"
^/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": "const numbers: number[] = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];\nconst sortedNumbers: number[] = numbers.sort((a, b) => a - b);\nconsole.log(sortedNumbers);",
"start": {
"line": 1,
"offset": 1
},
"end": {
"line": 29,
"offset": 28
}
}
]
}
],
"openFiles": [
{
"file": "^/inmemory/model/4",
"fileContent": "const numbers: number[] = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];\nconst sortedNumbers: number[] = numbers.sort((a, b) => a - b);\nconsole.log(sortedNumbers);",
"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 "const numbers: number[] = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];\nconst sortedNumbers: number[] = numbers.sort((a, b) => a - b);\nconsole.log(sortedNumbers);"
^/inmemory/model/4 SVC-2-0 "const numbers: number[] = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];\nconst sortedNumbers: number[] = numbers.sort((a, b) => a - b);\nconsole.log(sortedNumbers);"


^/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

0 comments on commit 6f6f29a

Please sign in to comment.