mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
initial scribbles
This commit is contained in:
@@ -1746,11 +1746,11 @@ namespace ts.server {
|
||||
if (configFileExistenceInfo) {
|
||||
const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile?.get(info.path);
|
||||
|
||||
// Delete the info from map, since this file is no more open
|
||||
// Delete the info from map, since this file is not open anymore
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile?.delete(info.path);
|
||||
|
||||
// If the script info was not root of inferred project,
|
||||
// there wont be config file watch open because of this script info
|
||||
// there won't be config file watch open because of this script info
|
||||
if (infoIsRootOfInferredProject) {
|
||||
// But if it is a root, it could be the last script info that is root of inferred project
|
||||
// and hence we would need to close the config file watcher
|
||||
@@ -3685,6 +3685,66 @@ namespace ts.server {
|
||||
return files;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
updateFileSystem(createdFiles: Iterator<protocol.FileSystemRequestArgs> | undefined, updatedFiles?: Iterator<protocol.FileSystemRequestArgs>, deletedFiles?: string[]) {
|
||||
// TODO: Maybe it is somehow gauche or verboten to use protocol types but the translation in applyChangesInOpenFiles seems stupid
|
||||
// It is also WEIRD that we use iterators here not arrays
|
||||
// TODO: Probably copy what vfsUtil does to hook up a virtual filesystem here. Maybe.
|
||||
// ugggggggggg have to copy over all that stuff to here
|
||||
// (though I probably need to create a vfs project anyway, so why not)
|
||||
|
||||
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
|
||||
files: {
|
||||
[builtFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "built/local"), resolver),
|
||||
[testLibFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "tests/lib"), resolver),
|
||||
[projectsFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "tests/projects"), resolver),
|
||||
[srcFolder]: {}
|
||||
},
|
||||
cwd: srcFolder,
|
||||
meta: { defaultLibLocation: builtFolder }
|
||||
})
|
||||
if (!this.fs)
|
||||
this.fs = fs
|
||||
if (createdFiles) {
|
||||
for (const document of Array.from(createdFiles)) {
|
||||
fs.mkdirpSync(vpath.dirname(document.file));
|
||||
fs.writeFileSync(document.file, document.text, "utf8");
|
||||
fs.filemeta(document.file).set("document", document);
|
||||
// Add symlinks
|
||||
const symlink = document.meta.get("symlink");
|
||||
if (symlink) {
|
||||
for (const link of symlink.split(",").map(link => link.trim())) {
|
||||
fs.mkdirpSync(vpath.dirname(link));
|
||||
fs.symlinkSync(vpath.resolve(fs.cwd(), document.file), link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. set some internal tsserver state for mocked FS (if it hasn't already been set, this might not be the first message)
|
||||
// - change this.host at least
|
||||
this.host // no, it's readonly, we get this from a parent object
|
||||
// - ScriptInfo instances might need to update their host -- what is textStorage?
|
||||
// - they deffo have FileWatcher instances
|
||||
// - TextStorage.host needs to update
|
||||
// 2. create
|
||||
// 3. update
|
||||
// 4. delete
|
||||
// - closeClientFile -> closeOpenFile -> ScriptInfo.close
|
||||
// 5. ???
|
||||
// 6. success!
|
||||
if (createdFiles) {
|
||||
}
|
||||
if (updatedFiles) {
|
||||
}
|
||||
if (deletedFiles) {
|
||||
for (const file of deletedFiles) {
|
||||
this.closeClientFile(file) // maybe copy stuff from applyChanges -- mostly I just want to leave a pointer to more code
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
applyChangesInOpenFiles(openFiles: Iterator<OpenFileArguments> | undefined, changedFiles?: Iterator<ChangeFileArguments>, closedFiles?: string[]): void {
|
||||
let openScriptInfos: ScriptInfo[] | undefined;
|
||||
|
||||
+34
-2
@@ -96,6 +96,7 @@ namespace ts.server.protocol {
|
||||
/* @internal */
|
||||
ApplyChangedToOpenFiles = "applyChangedToOpenFiles",
|
||||
UpdateOpen = "updateOpen",
|
||||
UpdateFileSystem = "updateFileSystem",
|
||||
/* @internal */
|
||||
EncodedSyntacticClassificationsFull = "encodedSyntacticClassifications-full",
|
||||
/* @internal */
|
||||
@@ -154,7 +155,7 @@ namespace ts.server.protocol {
|
||||
PrepareCallHierarchy = "prepareCallHierarchy",
|
||||
ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls",
|
||||
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
|
||||
ProvideInlayHints = "provideInlayHints"
|
||||
ProvideInlayHints = "provideInlayHints",
|
||||
|
||||
// NOTE: If updating this, be sure to also update `allCommandNames` in `testRunner/unittests/tsserver/session.ts`.
|
||||
}
|
||||
@@ -1805,6 +1806,7 @@ namespace ts.server.protocol {
|
||||
|
||||
/**
|
||||
* Request to synchronize list of open files with the client
|
||||
* TODO: Lots of unit tests refer to this too, a good starting point for UpdateFileSystemRequest
|
||||
*/
|
||||
export interface UpdateOpenRequest extends Request {
|
||||
command: CommandTypes.UpdateOpen;
|
||||
@@ -1820,7 +1822,7 @@ namespace ts.server.protocol {
|
||||
*/
|
||||
openFiles?: OpenRequestArgs[];
|
||||
/**
|
||||
* List of open files files that were changes
|
||||
* List of open files files that were changed
|
||||
*/
|
||||
changedFiles?: FileCodeEdits[];
|
||||
/**
|
||||
@@ -1829,6 +1831,36 @@ namespace ts.server.protocol {
|
||||
closedFiles?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateFileSystemRequest extends Request {
|
||||
command: CommandTypes.UpdateFileSystem;
|
||||
arguments: UpdateFileSystemRequestArgs;
|
||||
}
|
||||
|
||||
export interface UpdateFileSystemRequestArgs {
|
||||
/** For now, only 'memfs', initially for exclusive in-memory operation, but it could be other in-memory names later */
|
||||
fileSystem: string;
|
||||
/** For now, a list of newly created or newly available files. Probably need to ADD mocked file watchers */
|
||||
created: FileSystemRequestArgs[];
|
||||
/** Just-deleted files. Also needs to trigger and then remove file watchers (I think) */
|
||||
deleted: string[];
|
||||
/** Needs to replace what file watchers would normally listen to */
|
||||
updated: FileSystemRequestArgs[];
|
||||
}
|
||||
|
||||
export interface FileSystemRequestArgs extends FileRequestArgs {
|
||||
/**
|
||||
* Used to replace the content that would be on disk.
|
||||
* Then the known content will be used upon opening instead of the disk copy
|
||||
*/
|
||||
fileContent?: string;
|
||||
/**
|
||||
* Used to specify the script kind of the file explicitly. It could be one of the following:
|
||||
* "TS", "JS", "TSX", "JSX"
|
||||
* TODO: Not 100% sure this is needed.
|
||||
*/
|
||||
scriptKindName?: ScriptKindName;
|
||||
}
|
||||
|
||||
/**
|
||||
* External projects have a typeAcquisition option so they need to be added separately to compiler options for inferred projects.
|
||||
*/
|
||||
|
||||
+20
-9
@@ -714,6 +714,7 @@ namespace ts.server {
|
||||
|
||||
export class Session<TMessage = string> implements EventSender {
|
||||
private readonly gcTimer: GcTimer;
|
||||
// TODO: This will need to be replaced too
|
||||
protected projectService: ProjectService;
|
||||
private changeSeq = 0;
|
||||
|
||||
@@ -722,6 +723,7 @@ namespace ts.server {
|
||||
private currentRequestId!: number;
|
||||
private errorCheck: MultistepOperation;
|
||||
|
||||
// TODO: Replace this one?
|
||||
protected host: ServerHost;
|
||||
private readonly cancellationToken: ServerCancellationToken;
|
||||
protected readonly typingsInstaller: ITypingsInstaller;
|
||||
@@ -2643,16 +2645,16 @@ namespace ts.server {
|
||||
[CommandNames.UpdateOpen]: (request: protocol.UpdateOpenRequest) => {
|
||||
this.changeSeq++;
|
||||
this.projectService.applyChangesInOpenFiles(
|
||||
request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), file => ({
|
||||
fileName: file.file,
|
||||
content: file.fileContent,
|
||||
scriptKind: file.scriptKindName,
|
||||
projectRootPath: file.projectRootPath
|
||||
request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), ({ file, fileContent, scriptKindName, projectRootPath }) => ({
|
||||
fileName: file,
|
||||
content: fileContent,
|
||||
scriptKind: scriptKindName,
|
||||
projectRootPath
|
||||
})),
|
||||
request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
|
||||
fileName: file.fileName,
|
||||
changes: mapDefinedIterator(arrayReverseIterator(file.textChanges), change => {
|
||||
const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(file.fileName));
|
||||
request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), ({ fileName, textChanges}) => ({
|
||||
fileName,
|
||||
changes: mapDefinedIterator(arrayReverseIterator(textChanges), change => {
|
||||
const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(fileName));
|
||||
const start = scriptInfo.lineOffsetToPosition(change.start.line, change.start.offset);
|
||||
const end = scriptInfo.lineOffsetToPosition(change.end.line, change.end.offset);
|
||||
return start >= 0 ? { span: { start, length: end - start }, newText: change.newText } : undefined;
|
||||
@@ -2676,6 +2678,15 @@ namespace ts.server {
|
||||
// TODO: report errors
|
||||
return this.requiredResponse(/*response*/ true);
|
||||
},
|
||||
[CommandNames.UpdateFileSystem]: (request: protocol.UpdateFileSystemRequest) => {
|
||||
this.changeSeq++;
|
||||
this.projectService.updateFileSystem(
|
||||
request.arguments.created && arrayIterator(request.arguments.created), // open
|
||||
request.arguments.updated && arrayIterator(request.arguments.updated), //change
|
||||
request.arguments.deleted, // close
|
||||
);
|
||||
return this.requiredResponse(/*response*/ true)
|
||||
},
|
||||
[CommandNames.Exit]: () => {
|
||||
this.exit();
|
||||
return this.notRequired();
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"references": [
|
||||
{ "path": "../compiler" },
|
||||
{ "path": "../jsTyping" },
|
||||
{ "path": "../services" }
|
||||
{ "path": "../services" },
|
||||
{ "path": "../vfs" }
|
||||
],
|
||||
"files": [
|
||||
"types.ts",
|
||||
|
||||
@@ -1,4 +1,139 @@
|
||||
namespace ts.projectSystem {
|
||||
// TODO: Make a separate file at some point
|
||||
describe("unittests:: tsserver:: updateFileSystem", () => {
|
||||
interface Verify {
|
||||
applyChangesToOpen: (session: TestSession) => void;
|
||||
openFile1Again: (session: TestSession) => void;
|
||||
}
|
||||
const configFile: protocol.FileSystemRequestArgs = {
|
||||
file: "/a/b/tsconfig.json",
|
||||
fileContent: "{}"
|
||||
};
|
||||
const file3: protocol.FileSystemRequestArgs = {
|
||||
file: "/a/b/file3.ts",
|
||||
fileContent: "export let xyz = 1;"
|
||||
};
|
||||
const app: protocol.FileSystemRequestArgs = {
|
||||
file: "/a/b/app.ts",
|
||||
fileContent: "import { xyz } from './file3'; let x = xyz"
|
||||
};
|
||||
function fileContentWithComment(file: protocol.FileSystemRequestArgs | File) {
|
||||
return `// some copy right notice
|
||||
${'content' in file ? file.content : file.fileContent}`;
|
||||
}
|
||||
function verify({ applyChangesToOpen, openFile1Again }: Verify) {
|
||||
// TODO: Replace with createMemfsServerHost
|
||||
const host = createServerHost([commonFile1, commonFile2, libFile]);
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
|
||||
command: protocol.CommandTypes.UpdateFileSystem,
|
||||
arguments:{
|
||||
fileSystem: 'memfs',
|
||||
created: [configFile, file3, app],
|
||||
deleted: [], // string[];
|
||||
updated: [], //FileSystemRequestArgs[];
|
||||
}
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
// session.host
|
||||
const project = service.configuredProjects.get(configFile.file)!;
|
||||
assert.isDefined(project);
|
||||
verifyProjectVersion(project, 1);
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: app
|
||||
});
|
||||
verifyProjectVersion(project, 2);
|
||||
|
||||
// TODO: Verify file watchers have updated
|
||||
// Verify Texts
|
||||
verifyText(service, commonFile1.path, commonFile1.content);
|
||||
verifyText(service, commonFile2.path, commonFile2.content);
|
||||
verifyText(service, app.file, app.fileContent!);
|
||||
verifyText(service, file3.file, fileContentWithComment(file3));
|
||||
|
||||
// Apply changes
|
||||
applyChangesToOpen(session);
|
||||
|
||||
// Verify again
|
||||
verifyProjectVersion(project, 3);
|
||||
// Open file contents
|
||||
verifyText(service, commonFile1.path, fileContentWithComment(commonFile1));
|
||||
verifyText(service, commonFile2.path, fileContentWithComment(commonFile2));
|
||||
verifyText(service, app.file, "let zzz = 10;let zz = 10;let z = 1;");
|
||||
verifyText(service, file3.file, file3.fileContent!);
|
||||
|
||||
// Open file1 again
|
||||
openFile1Again(session);
|
||||
assert.isTrue(service.getScriptInfo(commonFile1.path)!.isScriptOpen());
|
||||
|
||||
// Verify that file1 contents are changed
|
||||
verifyProjectVersion(project, 4);
|
||||
verifyText(service, commonFile1.path, commonFile1.content);
|
||||
verifyText(service, commonFile2.path, fileContentWithComment(commonFile2));
|
||||
verifyText(service, app.file, "let zzz = 10;let zz = 10;let z = 1;");
|
||||
verifyText(service, file3.file, file3.fileContent!);
|
||||
}
|
||||
|
||||
function verifyText(service: server.ProjectService, file: string, expected: string) {
|
||||
const info = service.getScriptInfo(file)!;
|
||||
const snap = info.getSnapshot();
|
||||
// Verified applied in reverse order
|
||||
assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`);
|
||||
}
|
||||
|
||||
function verifyProjectVersion(project: server.Project, expected: number) {
|
||||
assert.equal(Number(project.getProjectVersion()), expected);
|
||||
}
|
||||
it("with updateOpen request", () => {
|
||||
verify({
|
||||
applyChangesToOpen: session => session.executeCommandSeq<protocol.UpdateOpenRequest>({
|
||||
command: protocol.CommandTypes.UpdateOpen,
|
||||
arguments: {
|
||||
openFiles: [
|
||||
{
|
||||
file: commonFile1.path,
|
||||
fileContent: fileContentWithComment(commonFile1)
|
||||
},
|
||||
{
|
||||
file: commonFile2.path,
|
||||
fileContent: fileContentWithComment(commonFile2)
|
||||
}
|
||||
],
|
||||
changedFiles: [
|
||||
{
|
||||
fileName: app.file,
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: "let zzz = 10;",
|
||||
},
|
||||
{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: "let zz = 10;",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
closedFiles: [
|
||||
file3.file
|
||||
]
|
||||
}
|
||||
}),
|
||||
openFile1Again: session => session.executeCommandSeq<protocol.UpdateOpenRequest>({
|
||||
command: protocol.CommandTypes.UpdateOpen,
|
||||
arguments: {
|
||||
openFiles: [{
|
||||
file: commonFile1.path,
|
||||
fileContent: commonFile1.content
|
||||
}]
|
||||
}
|
||||
}),
|
||||
});
|
||||
});
|
||||
})
|
||||
describe("unittests:: tsserver:: applyChangesToOpenFiles", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
|
||||
@@ -278,7 +278,8 @@ namespace ts.server {
|
||||
CommandNames.ToggleMultilineComment,
|
||||
CommandNames.CommentSelection,
|
||||
CommandNames.UncommentSelection,
|
||||
CommandNames.ProvideInlayHints
|
||||
CommandNames.ProvideInlayHints,
|
||||
CommandNames.UpdateFileSystem,
|
||||
];
|
||||
|
||||
it("should not throw when commands are executed with invalid arguments", () => {
|
||||
|
||||
Reference in New Issue
Block a user