diff --git a/src/server/client.ts b/src/server/client.ts index caeab6e3f3cf5..251b626eed5f1 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -1,5 +1,5 @@ /// - + namespace ts.server { export interface SessionClientHost extends LanguageServiceHost { @@ -25,23 +25,23 @@ namespace ts.server { private lineMaps: ts.Map = {}; 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; } @@ -82,34 +82,29 @@ namespace ts.server { } private processResponse(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); @@ -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); } @@ -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 = { @@ -199,13 +212,13 @@ namespace ts.server { var request = this.processRequest(CommandNames.Completions, args); var response = this.processResponse(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 = { @@ -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 || "", @@ -264,7 +277,7 @@ namespace ts.server { var request = this.processRequest(CommandNames.Format, args); var response = this.processResponse(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[] { @@ -284,7 +297,7 @@ namespace ts.server { var request = this.processRequest(CommandNames.Formatonkey, args); var response = this.processResponse(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[] { @@ -339,7 +352,7 @@ namespace ts.server { }); } - findReferences(fileName: string, position: number): ReferencedSymbol[]{ + findReferences(fileName: string, position: number): ReferencedSymbol[] { // Not yet implemented. return []; } @@ -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, @@ -478,10 +491,10 @@ namespace ts.server { line: lineOffset.line, offset: lineOffset.offset }; - + var request = this.processRequest(CommandNames.SignatureHelp, args); var response = this.processResponse(request); - + if (!response.body) { return undefined; } @@ -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: { @@ -499,7 +512,7 @@ namespace ts.server { }, selectedItemIndex: helpItems.selectedItemIndex, argumentIndex: helpItems.argumentIndex, - argumentCount: helpItems.argumentCount, + argumentCount: helpItems.argumentCount, } return result; } @@ -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[] { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 77908779d9fd7..62c8bf3ea1c0d 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -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 { @@ -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(); @@ -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 }; } } @@ -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); diff --git a/tests/cases/fourslash/server/projectWithNonExistentFiles.ts b/tests/cases/fourslash/server/projectWithNonExistentFiles.ts new file mode 100644 index 0000000000000..ceba136bf964e --- /dev/null +++ b/tests/cases/fourslash/server/projectWithNonExistentFiles.ts @@ -0,0 +1,13 @@ +/// + +// @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"])