Skip to content

Commit 353ccb7

Browse files
authored
Ensure correct script kind and text when using cached sourceFile from scriptInfo (#57641)
1 parent 0f6f7c3 commit 353ccb7

File tree

4 files changed

+487
-29
lines changed

4 files changed

+487
-29
lines changed

src/harness/incrementalUtils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,14 +436,17 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
436436
compilerHost.readFile = fileName => {
437437
const path = project.toPath(fileName);
438438
const info = project.projectService.filenameToScriptInfo.get(path);
439-
if (info?.isDynamicOrHasMixedContent() || project.fileIsOpen(path)) {
440-
return ts.getSnapshotText(info!.getSnapshot());
439+
if (info?.isDynamicOrHasMixedContent()) {
440+
return ts.getSnapshotText(info.getSnapshot());
441441
}
442442
if (!ts.isAnySupportedFileExtension(path)) {
443443
// Some external file
444444
const snapshot = project.getScriptSnapshot(path);
445445
return snapshot ? ts.getSnapshotText(snapshot) : undefined;
446446
}
447+
if (project.fileIsOpen(path)) {
448+
return ts.getSnapshotText(info!.getSnapshot());
449+
}
447450
// Read only rooted disk paths from host similar to ProjectService
448451
if (!ts.isRootedDiskPath(fileName) || !compilerHost.fileExists(fileName)) return undefined;
449452
if (ts.hasTSFileExtension(fileName)) return readFile(fileName);

src/services/documentRegistry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getKeyForCompilerOptions,
1414
getOrUpdate,
1515
getSetExternalModuleIndicator,
16+
getSnapshotText,
1617
identity,
1718
IScriptSnapshot,
1819
isDeclarationFileName,
@@ -300,7 +301,7 @@ export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boole
300301
let entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind);
301302
if (!entry && externalCache) {
302303
const sourceFile = externalCache.getDocument(keyWithMode, path);
303-
if (sourceFile) {
304+
if (sourceFile && sourceFile.scriptKind === scriptKind && sourceFile.text === getSnapshotText(scriptSnapshot)) {
304305
Debug.assert(acquiring);
305306
entry = {
306307
sourceFile,

src/testRunner/unittests/tsserver/plugins.ts

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
baselineTsserverLogs,
88
openFilesForSession,
99
TestSession,
10+
verifyGetErrRequest,
1011
} from "../helpers/tsserver";
1112
import {
1213
createServerHost,
@@ -282,6 +283,35 @@ describe("unittests:: tsserver:: plugins:: overriding getSupportedCodeFixes", ()
282283
});
283284

284285
describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
286+
function createGetExternalFiles(getSession: () => TestSession) {
287+
const externalFiles = new Map<ts.server.Project, string[]>();
288+
return (project: ts.server.Project, updateLevel: ts.ProgramUpdateLevel) => {
289+
if (project.projectKind !== ts.server.ProjectKind.Configured) return [];
290+
if (updateLevel === ts.ProgramUpdateLevel.Update) {
291+
const existing = externalFiles.get(project);
292+
if (existing) {
293+
getSession().logger.log(`getExternalFiles:: Returning cached .vue files`);
294+
return existing;
295+
}
296+
}
297+
getSession().logger.log(`getExternalFiles:: Getting new list of .vue files`);
298+
const configFile = project.getProjectName();
299+
const config = ts.readJsonConfigFile(configFile, project.readFile.bind(project));
300+
const parseHost: ts.ParseConfigHost = {
301+
useCaseSensitiveFileNames: project.useCaseSensitiveFileNames(),
302+
fileExists: project.fileExists.bind(project),
303+
readFile: project.readFile.bind(project),
304+
readDirectory: (...args) => {
305+
args[1] = [".vue"];
306+
return project.readDirectory(...args);
307+
},
308+
};
309+
const parsed = ts.parseJsonSourceFileConfigFileContent(config, parseHost, project.getCurrentDirectory());
310+
externalFiles.set(project, parsed.fileNames);
311+
return parsed.fileNames;
312+
};
313+
}
314+
285315
it("new files with non ts extensions and wildcard matching", () => {
286316
const aTs: File = {
287317
path: "/user/username/projects/myproject/a.ts",
@@ -303,7 +333,6 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
303333
}),
304334
};
305335
const host = createServerHost([aTs, dTs, bVue, config, libFile]);
306-
const externalFiles = new Map<ts.server.Project, string[]>();
307336
host.require = () => {
308337
return {
309338
module: () => ({
@@ -321,31 +350,7 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
321350
originalGetScriptSnapshot(fileName);
322351
return proxy;
323352
},
324-
getExternalFiles: (project: ts.server.Project, updateLevel: ts.ProgramUpdateLevel) => {
325-
if (project.projectKind !== ts.server.ProjectKind.Configured) return [];
326-
if (updateLevel === ts.ProgramUpdateLevel.Update) {
327-
const existing = externalFiles.get(project);
328-
if (existing) {
329-
session.logger.log(`getExternalFiles:: Returning cached .vue files`);
330-
return existing;
331-
}
332-
}
333-
session.logger.log(`getExternalFiles:: Getting new list of .vue files`);
334-
const configFile = project.getProjectName();
335-
const config = ts.readJsonConfigFile(configFile, project.readFile.bind(project));
336-
const parseHost: ts.ParseConfigHost = {
337-
useCaseSensitiveFileNames: project.useCaseSensitiveFileNames(),
338-
fileExists: project.fileExists.bind(project),
339-
readFile: project.readFile.bind(project),
340-
readDirectory: (...args) => {
341-
args[1] = [".vue"];
342-
return project.readDirectory(...args);
343-
},
344-
};
345-
const parsed = ts.parseJsonSourceFileConfigFileContent(config, parseHost, project.getCurrentDirectory());
346-
externalFiles.set(project, parsed.fileNames);
347-
return parsed.fileNames;
348-
},
353+
getExternalFiles: createGetExternalFiles(() => session),
349354
}),
350355
error: undefined,
351356
};
@@ -361,4 +366,67 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
361366

362367
baselineTsserverLogs("plugins", "new files with non ts extensions with wildcard matching", session);
363368
});
369+
370+
it("when scriptKind changes for the external file", () => {
371+
const aTs: File = {
372+
path: "/user/username/projects/myproject/a.ts",
373+
content: `export const a = 10;`,
374+
};
375+
const bVue: File = {
376+
path: "/user/username/projects/myproject/b.vue",
377+
content: "bVueFile",
378+
};
379+
const config: File = {
380+
path: "/user/username/projects/myproject/tsconfig.json",
381+
content: jsonToReadableText({
382+
compilerOptions: { composite: true },
383+
include: ["*.ts", "*.vue"],
384+
}),
385+
};
386+
const host = createServerHost([aTs, bVue, config, libFile]);
387+
let currentVueScriptKind = ts.ScriptKind.TS;
388+
host.require = () => {
389+
return {
390+
module: () => ({
391+
create(info: ts.server.PluginCreateInfo) {
392+
const proxy = Harness.LanguageService.makeDefaultProxy(info);
393+
const originalScriptKind = info.languageServiceHost.getScriptKind!.bind(info.languageServiceHost);
394+
info.languageServiceHost.getScriptKind = fileName =>
395+
fileName === bVue.path ?
396+
currentVueScriptKind :
397+
originalScriptKind(fileName);
398+
const originalGetScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
399+
info.languageServiceHost.getScriptSnapshot = fileName =>
400+
fileName === bVue.path ?
401+
ts.ScriptSnapshot.fromString(`import { y } from "${bVue.content}";`) : // Change the text so that imports change and we need to reconstruct program
402+
originalGetScriptSnapshot(fileName);
403+
return proxy;
404+
},
405+
getExternalFiles: createGetExternalFiles(() => session),
406+
}),
407+
error: undefined,
408+
};
409+
};
410+
const session = new TestSession({ host, globalPlugins: ["myplugin"] });
411+
openFilesForSession([bVue], session);
412+
413+
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
414+
command: ts.server.protocol.CommandTypes.UpdateOpen,
415+
arguments: {
416+
changedFiles: [{
417+
fileName: bVue.path,
418+
textChanges: [{
419+
start: { line: 1, offset: bVue.content.length + 1 },
420+
end: { line: 1, offset: bVue.content.length + 1 },
421+
newText: "Updated",
422+
}],
423+
}],
424+
},
425+
});
426+
bVue.content += "Updated";
427+
currentVueScriptKind = ts.ScriptKind.JS;
428+
429+
verifyGetErrRequest({ session, files: [bVue] });
430+
baselineTsserverLogs("plugins", "when scriptKind changes for the external file", session);
431+
});
364432
});

0 commit comments

Comments
 (0)