Skip to content

Tolerate non-existing files specified for tsserver #8504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 60 additions & 47 deletions src/server/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference path="session.ts" />

namespace ts.server {

export interface SessionClientHost extends LanguageServiceHost {
Expand All @@ -25,23 +25,23 @@ namespace ts.server {
private lineMaps: ts.Map<number[]> = {};
private messages: string[] = [];
private lastRenameEntry: RenameEntry;

constructor(private host: SessionClientHost) {
}

public onMessage(message: string): void {
public onMessage(message: string): void {
this.messages.push(message);
}

private writeMessage(message: string): void {
private writeMessage(message: string): void {
this.host.writeMessage(message);
}

private getLineMap(fileName: string): number[] {
private getLineMap(fileName: string): number[] {
var lineMap = ts.lookUp(this.lineMaps, fileName);
if (!lineMap) {
var scriptSnapshot = this.host.getScriptSnapshot(fileName);
lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
}
return lineMap;
}
Expand Down Expand Up @@ -82,34 +82,29 @@ namespace ts.server {
}

private processResponse<T extends protocol.Response>(request: protocol.Request): T {
var lastMessage = this.messages.shift();
Debug.assert(!!lastMessage, "Did not receive any responses.");

// Read the content length
var contentLengthPrefix = "Content-Length: ";
var lines = lastMessage.split("\r\n");
Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response.");

var contentLengthText = lines[0];
Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header.");
var contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length));

// Read the body
var responseBody = lines[2];

// Verify content length
Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length.");

try {
var response: T = JSON.parse(responseBody);
}
catch (e) {
throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message);
let foundResponseMessage = false;
let lastMessage: string;
let response: T;
while (!foundResponseMessage) {
lastMessage = this.messages.shift();
Debug.assert(!!lastMessage, "Did not receive any responses.");
const responseBody = processMessage(lastMessage);
try {
response = JSON.parse(responseBody);
// the server may emit events before emitting the response. We
// want to ignore these events for testing purpose.
if (response.type === "response") {
foundResponseMessage = true;
}
}
catch (e) {
throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message);
}
}

// verify the sequence numbers
Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number.");

// unmarshal errors
if (!response.success) {
throw new Error("Error " + response.message);
Expand All @@ -118,9 +113,27 @@ namespace ts.server {
Debug.assert(!!response.body, "Malformed response: Unexpected empty response body.");

return response;

function processMessage(message: string) {
// Read the content length
const contentLengthPrefix = "Content-Length: ";
const lines = message.split("\r\n");
Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response.");

const contentLengthText = lines[0];
Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header.");
const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length));

// Read the body
const responseBody = lines[2];

// Verify content length
Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length.");
return responseBody;
}
}

openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content, scriptKindName };
this.processRequest(CommandNames.Open, args);
}
Expand Down Expand Up @@ -186,7 +199,7 @@ namespace ts.server {
fileNames: response.body.fileNames
};
}

getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
var args: protocol.CompletionsRequestArgs = {
Expand All @@ -199,13 +212,13 @@ namespace ts.server {
var request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
var response = this.processResponse<protocol.CompletionsResponse>(request);

return {
return {
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: response.body
};
}

getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
var args: protocol.CompletionDetailsRequestArgs = {
Expand Down Expand Up @@ -234,7 +247,7 @@ namespace ts.server {
var fileName = entry.file;
var start = this.lineOffsetToPosition(fileName, entry.start);
var end = this.lineOffsetToPosition(fileName, entry.end);

return {
name: entry.name,
containerName: entry.containerName || "",
Expand Down Expand Up @@ -264,7 +277,7 @@ namespace ts.server {
var request = this.processRequest<protocol.FormatRequest>(CommandNames.Format, args);
var response = this.processResponse<protocol.FormatResponse>(request);

return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
}

getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
Expand All @@ -284,7 +297,7 @@ namespace ts.server {
var request = this.processRequest<protocol.FormatOnKeyRequest>(CommandNames.Formatonkey, args);
var response = this.processResponse<protocol.FormatResponse>(request);

return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
}

getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
Expand Down Expand Up @@ -339,7 +352,7 @@ namespace ts.server {
});
}

findReferences(fileName: string, position: number): ReferencedSymbol[]{
findReferences(fileName: string, position: number): ReferencedSymbol[] {
// Not yet implemented.
return [];
}
Expand Down Expand Up @@ -444,7 +457,7 @@ namespace ts.server {
text: item.text,
kind: item.kind,
kindModifiers: item.kindModifiers || "",
spans: item.spans.map(span=> createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
spans: item.spans.map(span => createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
childItems: this.decodeNavigationBarItems(item.childItems, fileName),
indent: 0,
bolded: false,
Expand Down Expand Up @@ -478,10 +491,10 @@ namespace ts.server {
line: lineOffset.line,
offset: lineOffset.offset
};

var request = this.processRequest<protocol.SignatureHelpRequest>(CommandNames.SignatureHelp, args);
var response = this.processResponse<protocol.SignatureHelpResponse>(request);

if (!response.body) {
return undefined;
}
Expand All @@ -490,7 +503,7 @@ namespace ts.server {
var span = helpItems.applicableSpan;
var start = this.lineOffsetToPosition(fileName, span.start);
var end = this.lineOffsetToPosition(fileName, span.end);

var result: SignatureHelpItems = {
items: helpItems.items,
applicableSpan: {
Expand All @@ -499,7 +512,7 @@ namespace ts.server {
},
selectedItemIndex: helpItems.selectedItemIndex,
argumentIndex: helpItems.argumentIndex,
argumentCount: helpItems.argumentCount,
argumentCount: helpItems.argumentCount,
}
return result;
}
Expand Down Expand Up @@ -561,15 +574,15 @@ namespace ts.server {
}

getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
throw new Error("Not Implemented Yet.");
throw new Error("Not Implemented Yet.");
}

getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
throw new Error("Not Implemented Yet.");
throw new Error("Not Implemented Yet.");
}

isValidBraceCompletionAtPostion(fileName: string, position: number, openingBrace: number): boolean {
throw new Error("Not Implemented Yet.");
throw new Error("Not Implemented Yet.");
}

getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
Expand Down
13 changes: 9 additions & 4 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1122,8 +1122,13 @@ namespace ts.server {
return { configFileName, configFileErrors: configResult.errors };
}
else {
// even if opening config file was successful, it could still
// contain errors that were tolerated.
this.log("Opened configuration file " + configFileName, "Info");
this.configuredProjects.push(configResult.project);
if (configResult.errors && configResult.errors.length > 0) {
return { configFileName, configFileErrors: configResult.errors };
}
}
}
else {
Expand Down Expand Up @@ -1261,14 +1266,14 @@ namespace ts.server {
}
else {
const project = this.createProject(configFilename, projectOptions);
let errors: Diagnostic[];
for (const rootFilename of projectOptions.files) {
if (this.host.fileExists(rootFilename)) {
const info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename);
project.addRoot(info);
}
else {
const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, rootFilename);
return { success: false, errors: [error] };
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.File_0_not_found, rootFilename));
}
}
project.finishGraph();
Expand All @@ -1279,7 +1284,7 @@ namespace ts.server {
path => this.directoryWatchedForSourceFilesChanged(project, path),
/*recursive*/ true
);
return { success: true, project: project };
return { success: true, project: project, errors };
}
}

Expand All @@ -1295,7 +1300,7 @@ namespace ts.server {
}
else {
const oldFileNames = project.compilerService.host.roots.map(info => info.fileName);
const newFileNames = projectOptions.files;
const newFileNames = ts.filter(projectOptions.files, f => this.host.fileExists(f));
const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0);
const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0);

Expand Down
13 changes: 13 additions & 0 deletions tests/cases/fourslash/server/projectWithNonExistentFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path="../fourslash.ts"/>

// @Filename: a.ts
////export var test = "test String"

// @Filename: b.ts
////export var test2 = "test String"

// @Filename: tsconfig.json
////{ "files": ["a.ts", "c.ts", "b.ts"] }

goTo.file("a.ts");
verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts"])