Skip to content

Commit

Permalink
Ensure correct script kind and text when using cached sourceFile from…
Browse files Browse the repository at this point in the history
… scriptInfo (#57641)
  • Loading branch information
sheetalkamat committed Mar 4, 2024
1 parent 0f6f7c3 commit 353ccb7
Show file tree
Hide file tree
Showing 4 changed files with 487 additions and 29 deletions.
7 changes: 5 additions & 2 deletions src/harness/incrementalUtils.ts
Expand Up @@ -436,14 +436,17 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
compilerHost.readFile = fileName => {
const path = project.toPath(fileName);
const info = project.projectService.filenameToScriptInfo.get(path);
if (info?.isDynamicOrHasMixedContent() || project.fileIsOpen(path)) {
return ts.getSnapshotText(info!.getSnapshot());
if (info?.isDynamicOrHasMixedContent()) {
return ts.getSnapshotText(info.getSnapshot());
}
if (!ts.isAnySupportedFileExtension(path)) {
// Some external file
const snapshot = project.getScriptSnapshot(path);
return snapshot ? ts.getSnapshotText(snapshot) : undefined;
}
if (project.fileIsOpen(path)) {
return ts.getSnapshotText(info!.getSnapshot());
}
// Read only rooted disk paths from host similar to ProjectService
if (!ts.isRootedDiskPath(fileName) || !compilerHost.fileExists(fileName)) return undefined;
if (ts.hasTSFileExtension(fileName)) return readFile(fileName);
Expand Down
3 changes: 2 additions & 1 deletion src/services/documentRegistry.ts
Expand Up @@ -13,6 +13,7 @@ import {
getKeyForCompilerOptions,
getOrUpdate,
getSetExternalModuleIndicator,
getSnapshotText,
identity,
IScriptSnapshot,
isDeclarationFileName,
Expand Down Expand Up @@ -300,7 +301,7 @@ export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boole
let entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind);
if (!entry && externalCache) {
const sourceFile = externalCache.getDocument(keyWithMode, path);
if (sourceFile) {
if (sourceFile && sourceFile.scriptKind === scriptKind && sourceFile.text === getSnapshotText(scriptSnapshot)) {
Debug.assert(acquiring);
entry = {
sourceFile,
Expand Down
120 changes: 94 additions & 26 deletions src/testRunner/unittests/tsserver/plugins.ts
Expand Up @@ -7,6 +7,7 @@ import {
baselineTsserverLogs,
openFilesForSession,
TestSession,
verifyGetErrRequest,
} from "../helpers/tsserver";
import {
createServerHost,
Expand Down Expand Up @@ -282,6 +283,35 @@ describe("unittests:: tsserver:: plugins:: overriding getSupportedCodeFixes", ()
});

describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
function createGetExternalFiles(getSession: () => TestSession) {
const externalFiles = new Map<ts.server.Project, string[]>();
return (project: ts.server.Project, updateLevel: ts.ProgramUpdateLevel) => {
if (project.projectKind !== ts.server.ProjectKind.Configured) return [];
if (updateLevel === ts.ProgramUpdateLevel.Update) {
const existing = externalFiles.get(project);
if (existing) {
getSession().logger.log(`getExternalFiles:: Returning cached .vue files`);
return existing;
}
}
getSession().logger.log(`getExternalFiles:: Getting new list of .vue files`);
const configFile = project.getProjectName();
const config = ts.readJsonConfigFile(configFile, project.readFile.bind(project));
const parseHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: project.useCaseSensitiveFileNames(),
fileExists: project.fileExists.bind(project),
readFile: project.readFile.bind(project),
readDirectory: (...args) => {
args[1] = [".vue"];
return project.readDirectory(...args);
},
};
const parsed = ts.parseJsonSourceFileConfigFileContent(config, parseHost, project.getCurrentDirectory());
externalFiles.set(project, parsed.fileNames);
return parsed.fileNames;
};
}

it("new files with non ts extensions and wildcard matching", () => {
const aTs: File = {
path: "/user/username/projects/myproject/a.ts",
Expand All @@ -303,7 +333,6 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
}),
};
const host = createServerHost([aTs, dTs, bVue, config, libFile]);
const externalFiles = new Map<ts.server.Project, string[]>();
host.require = () => {
return {
module: () => ({
Expand All @@ -321,31 +350,7 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
originalGetScriptSnapshot(fileName);
return proxy;
},
getExternalFiles: (project: ts.server.Project, updateLevel: ts.ProgramUpdateLevel) => {
if (project.projectKind !== ts.server.ProjectKind.Configured) return [];
if (updateLevel === ts.ProgramUpdateLevel.Update) {
const existing = externalFiles.get(project);
if (existing) {
session.logger.log(`getExternalFiles:: Returning cached .vue files`);
return existing;
}
}
session.logger.log(`getExternalFiles:: Getting new list of .vue files`);
const configFile = project.getProjectName();
const config = ts.readJsonConfigFile(configFile, project.readFile.bind(project));
const parseHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: project.useCaseSensitiveFileNames(),
fileExists: project.fileExists.bind(project),
readFile: project.readFile.bind(project),
readDirectory: (...args) => {
args[1] = [".vue"];
return project.readDirectory(...args);
},
};
const parsed = ts.parseJsonSourceFileConfigFileContent(config, parseHost, project.getCurrentDirectory());
externalFiles.set(project, parsed.fileNames);
return parsed.fileNames;
},
getExternalFiles: createGetExternalFiles(() => session),
}),
error: undefined,
};
Expand All @@ -361,4 +366,67 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {

baselineTsserverLogs("plugins", "new files with non ts extensions with wildcard matching", session);
});

it("when scriptKind changes for the external file", () => {
const aTs: File = {
path: "/user/username/projects/myproject/a.ts",
content: `export const a = 10;`,
};
const bVue: File = {
path: "/user/username/projects/myproject/b.vue",
content: "bVueFile",
};
const config: File = {
path: "/user/username/projects/myproject/tsconfig.json",
content: jsonToReadableText({
compilerOptions: { composite: true },
include: ["*.ts", "*.vue"],
}),
};
const host = createServerHost([aTs, bVue, config, libFile]);
let currentVueScriptKind = ts.ScriptKind.TS;
host.require = () => {
return {
module: () => ({
create(info: ts.server.PluginCreateInfo) {
const proxy = Harness.LanguageService.makeDefaultProxy(info);
const originalScriptKind = info.languageServiceHost.getScriptKind!.bind(info.languageServiceHost);
info.languageServiceHost.getScriptKind = fileName =>
fileName === bVue.path ?
currentVueScriptKind :
originalScriptKind(fileName);
const originalGetScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
info.languageServiceHost.getScriptSnapshot = fileName =>
fileName === bVue.path ?
ts.ScriptSnapshot.fromString(`import { y } from "${bVue.content}";`) : // Change the text so that imports change and we need to reconstruct program
originalGetScriptSnapshot(fileName);
return proxy;
},
getExternalFiles: createGetExternalFiles(() => session),
}),
error: undefined,
};
};
const session = new TestSession({ host, globalPlugins: ["myplugin"] });
openFilesForSession([bVue], session);

session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [{
fileName: bVue.path,
textChanges: [{
start: { line: 1, offset: bVue.content.length + 1 },
end: { line: 1, offset: bVue.content.length + 1 },
newText: "Updated",
}],
}],
},
});
bVue.content += "Updated";
currentVueScriptKind = ts.ScriptKind.JS;

verifyGetErrRequest({ session, files: [bVue] });
baselineTsserverLogs("plugins", "when scriptKind changes for the external file", session);
});
});

0 comments on commit 353ccb7

Please sign in to comment.