diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 77cc379acc2..cab5c21ad90 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,5 +1,6 @@ /// /// +/// /* @internal */ diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index a7437e34599..813ea189157 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -36,7 +36,7 @@ namespace ts { } safeFileList = ""; - postInstallActions: (( map: (t: string[]) => string[]) => void)[] = []; + postInstallActions: ((map: (t: string[]) => string[]) => void)[] = []; runPostInstallActions(map: (t: string[]) => string[]) { for (const f of this.postInstallActions) { @@ -281,7 +281,7 @@ namespace ts { private timeoutCallbacks = new Callbacks(); private immediateCallbacks = new Callbacks(); - readonly watchedDirectories = createMap<{ cb: DirectoryWatcherCallback, recursive: boolean }[]>(); + readonly watchedDirectories = createMap<{ cb: DirectoryWatcherCallback, recursive: boolean }[]>(); readonly watchedFiles = createMap(); private filesOrFolders: FileOrFolder[]; @@ -2011,7 +2011,7 @@ namespace ts { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = projectService.configuredProjects[0]; - checkProjectActualFiles(p, [ file1.path ]); + checkProjectActualFiles(p, [file1.path]); assert(host.fileExists(combinePaths(installer.cachePath, "tsd.json"))); @@ -2021,10 +2021,10 @@ namespace ts { return ["jquery/jquery.d.ts"]; }); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(p, [ file1.path, jquery.path ]); + checkProjectActualFiles(p, [file1.path, jquery.path]); }); - it ("inferred project (tsd installed)", () => { + it("inferred project (tsd installed)", () => { const file1 = { path: "/a/b/app.js", content: "" @@ -2051,7 +2051,7 @@ namespace ts { checkNumberOfProjects(projectService, { inferredProjects: 1 }); const p = projectService.inferredProjects[0]; - checkProjectActualFiles(p, [ file1.path ]); + checkProjectActualFiles(p, [file1.path]); assert(host.fileExists(combinePaths(installer.cachePath, "tsd.json"))); @@ -2061,10 +2061,10 @@ namespace ts { return ["jquery/jquery.d.ts"]; }); checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(p, [ file1.path, jquery.path ]); + checkProjectActualFiles(p, [file1.path, jquery.path]); }); - it ("external project - no typing options, no .d.ts/js files", () => { + it("external project - no typing options, no .d.ts/js files", () => { const file1 = { path: "/a/b/app.ts", content: "" @@ -2091,7 +2091,7 @@ namespace ts { projectService.checkNumberOfProjects({ externalProjects: 1 }); }); - it ("external project - no autoDiscovery in typing options, no .d.ts/js files", () => { + it("external project - no autoDiscovery in typing options, no .d.ts/js files", () => { const file1 = { path: "/a/b/app.ts", content: "" @@ -2119,7 +2119,7 @@ namespace ts { projectService.checkNumberOfProjects({ externalProjects: 1 }); }); - it ("external project - autoDiscovery = true, no .d.ts/js files", () => { + it("external project - autoDiscovery = true, no .d.ts/js files", () => { const file1 = { path: "/a/b/app.ts", content: "" @@ -2156,4 +2156,209 @@ namespace ts { assert.isTrue(runTsdIsCalled, "expected 'runTsdIsCalled' to be true"); }); }); + + describe("Project errors", () => { + function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: string[]) { + assert.isTrue(projectFiles !== undefined, "missing project files"); + const errors = projectFiles.projectErrors; + assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); + if (expectedErrors.length) { + for (let i = 0; i < errors.length; i++) { + const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n"); + const expectedMessage = expectedErrors[i]; + assert.equal(actualMessage, expectedMessage, "error message does not match"); + } + } + } + + it("external project - diagnostics for missing files", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + // only file1 exists - expect error + const host = createServerHost([file1]); + const projectService = createProjectService(host); + const projectFileName = "/a/b/test.csproj"; + + { + projectService.openExternalProject({ + projectFileName, + options: {}, + rootFiles: toExternalFiles([file1.path, file2.path]) + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + const knownProjects = projectService.synchronizeProjectList([]); + checkProjectErrors(knownProjects[0], ["File '/a/b/lib.ts' not found."]); + } + // only file2 exists - expect error + host.reloadFS([file2]); + { + projectService.openExternalProject({ + projectFileName, + options: {}, + rootFiles: toExternalFiles([file1.path, file2.path]) + }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + const knownProjects = projectService.synchronizeProjectList([]); + checkProjectErrors(knownProjects[0], ["File '/a/b/app.ts' not found."]); + } + + // both files exist - expect no errors + host.reloadFS([file1, file2]); + { + projectService.openExternalProject({ + projectFileName, + options: {}, + rootFiles: toExternalFiles([file1.path, file2.path]) + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + const knownProjects = projectService.synchronizeProjectList([]); + checkProjectErrors(knownProjects[0], []); + } + }); + + it("configured projects - diagnostics for missing files", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) + }; + const host = createServerHost([file1, config]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]); + + host.reloadFS([file1, file2, config]); + + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectErrors(projectService.synchronizeProjectList([])[0], []); + }); + + it("configured projects - diagnostics for corrupted config 1", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const correctConfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) + }; + const corruptedConfig = { + path: correctConfig.path, + content: correctConfig.content.substr(1) + }; + const host = createServerHost([file1, file2, corruptedConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + { + projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); + const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, [`Failed to parse file \'/a/b/tsconfig.json\': Unexpected token : in JSON at position 7.`]); + } + // fix config and trigger watcher + host.reloadFS([file1, file2, correctConfig]); + host.triggerFileWatcherCallback(correctConfig.path, /*false*/); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + } + }); + + it("configured projects - diagnostics for corrupted config 2", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const correctConfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) + }; + const corruptedConfig = { + path: correctConfig.path, + content: correctConfig.content.substr(1) + }; + const host = createServerHost([file1, file2, correctConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + } + // fix config and trigger watcher + host.reloadFS([file1, file2, corruptedConfig]); + host.triggerFileWatcherCallback(corruptedConfig.path, /*false*/); + { + projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); + const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, [`Failed to parse file \'/a/b/tsconfig.json\': Unexpected token : in JSON at position 7.`]); + } + }); + }); + + describe("Proper errors", () => { + it("document is not contained in project", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: "/a/b/tsconfig.json", + content: "{" + }; + const host = createServerHost([file1, corruptedConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); + + const project = projectService.findProject(corruptedConfig.path); + let expectedMessage: string; + try { + server.Errors.ThrowProjectDoesNotContainDocument(file1.path, project); + assert(false, "should not get there"); + } + catch (e) { + expectedMessage = (e).message; + } + try { + project.getScriptInfo(file1.path); + } + catch (e) { + assert.equal((e).message, expectedMessage, "Unexpected error"); + } + }); + }); } \ No newline at end of file diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index ba2d4386a75..267165f2657 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -30,7 +30,7 @@ namespace ts.server { interface ConfigFileConversionResult { success: boolean; - errors?: Diagnostic[]; + configFileErrors?: Diagnostic[]; projectOptions?: ProjectOptions; } @@ -73,6 +73,10 @@ namespace ts.server { } } + function createFileNotFoundDiagnostic(fileName: string) { + return createCompilerDiagnostic(Diagnostics.File_0_not_found, fileName); + } + /** * TODO: enforce invariants: * - script info can be never migrate to state - root file in inferred project, this is only a starting point @@ -655,7 +659,7 @@ namespace ts.server { const configObj = parseConfigFileTextToJson(configFilename, this.host.readFile(configFilename)); if (configObj.error) { - return { success: false, errors: [configObj.error] }; + return { success: false, configFileErrors: [configObj.error] }; } const parsedCommandLine = parseJsonConfigFileContent( @@ -668,12 +672,12 @@ namespace ts.server { Debug.assert(!!parsedCommandLine.fileNames); if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) { - return { success: false, errors: parsedCommandLine.errors }; + return { success: false, configFileErrors: parsedCommandLine.errors }; } if (parsedCommandLine.fileNames.length === 0) { const error = createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename); - return { success: false, errors: [error] }; + return { success: false, configFileErrors: [error] }; } const projectOptions: ProjectOptions = { @@ -714,10 +718,9 @@ namespace ts.server { /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(options, files, externalFilePropertyReader), options.compileOnSave === undefined ? true : options.compileOnSave); - const errors = this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typingOptions); - + this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typingOptions); this.externalProjects.push(project); - return { project, errors }; + return project; } private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, clientFileName?: string) { @@ -732,7 +735,7 @@ namespace ts.server { /*languageServiceEnabled*/ !sizeLimitExceeded, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave); - const errors = this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typingOptions); + this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typingOptions); project.watchConfigFile(project => this.onConfigChangedForConfiguredProject(project)); if (!sizeLimitExceeded) { @@ -741,7 +744,7 @@ namespace ts.server { project.watchWildcards((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); this.configuredProjects.push(project); - return { project, errors }; + return project; } private watchConfigDirectoryForProject(project: ConfiguredProject, options: ProjectOptions): void { @@ -750,7 +753,7 @@ namespace ts.server { } } - private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typingOptions: TypingOptions): Diagnostic[] { + private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typingOptions: TypingOptions): void { let errors: Diagnostic[]; for (const f of files) { const rootFilename = propertyReader.getFileName(f); @@ -761,32 +764,37 @@ namespace ts.server { project.addRoot(info); } else { - (errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.File_0_not_found, rootFilename)); + (errors || (errors = [])).push(createFileNotFoundDiagnostic(rootFilename)); } } + project.setProjectErrors(errors); project.setTypingOptions(typingOptions); project.updateGraph(); - return errors; } private openConfigFile(configFileName: NormalizedPath, clientFileName?: string): OpenConfigFileResult { const conversionResult = this.convertConfigFileContentToProjectOptions(configFileName); if (!conversionResult.success) { - return { success: false, errors: conversionResult.errors }; + // open project with no files and set errors on the project + const project = this.createAndAddConfiguredProject(configFileName, { files: [], compilerOptions: {} }, clientFileName); + project.setProjectErrors(conversionResult.configFileErrors); + return { success: false, errors: conversionResult.configFileErrors }; } - const { project, errors } = this.createAndAddConfiguredProject(configFileName, conversionResult.projectOptions, clientFileName); - return { success: true, project, errors }; + const project = this.createAndAddConfiguredProject(configFileName, conversionResult.projectOptions, clientFileName); + return { success: true, project, errors: project.getProjectErrors() }; } - private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean) { + private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean, configFileErrors: Diagnostic[]) { const oldRootScriptInfos = project.getRootScriptInfos(); const newRootScriptInfos: ScriptInfo[] = []; const newRootScriptInfoMap: NormalizedPathMap = createNormalizedPathMap(); + let projectErrors: Diagnostic[]; let rootFilesChanged = false; for (const f of newUncheckedFiles) { const newRootFile = propertyReader.getFileName(f); if (!this.host.fileExists(newRootFile)) { + (projectErrors || (projectErrors = [])).push(createFileNotFoundDiagnostic(newRootFile)); continue; } const normalizedPath = toNormalizedPath(newRootFile); @@ -840,6 +848,8 @@ namespace ts.server { project.setCompilerOptions(newOptions); (project).setTypingOptions(newTypingOptions); project.compileOnSaveEnabled = !!compileOnSave; + project.setProjectErrors(concatenate(configFileErrors, projectErrors)); + project.updateGraph(); } @@ -850,9 +860,11 @@ namespace ts.server { return; } - const { success, projectOptions, errors } = this.convertConfigFileContentToProjectOptions(project.configFileName); + const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.configFileName); if (!success) { - return errors; + // reset project settings to default + this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/false, configFileErrors); + return configFileErrors; } if (this.exceededTotalSizeLimitForNonTsFiles(projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { @@ -869,7 +881,7 @@ namespace ts.server { project.enableLanguageService(); } this.watchConfigDirectoryForProject(project, projectOptions); - this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typingOptions, projectOptions.compileOnSave); + this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typingOptions, projectOptions.compileOnSave, configFileErrors); } } @@ -1052,15 +1064,15 @@ namespace ts.server { this.printProjects(); } - private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: protocol.ProjectFiles[]): void { + private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void { for (const proj of currentProjects) { const knownProject = forEach(lastKnownProjectVersions, p => p.projectName === proj.getProjectName() && p); result.push(proj.getChangesSinceVersion(knownProject && knownProject.version)); } } - synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): protocol.ProjectFiles[] { - const files: protocol.ProjectFiles[] = []; + synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): ProjectFilesWithTSDiagnostics[] { + const files: ProjectFilesWithTSDiagnostics[] = []; this.collectChanges(knownProjects, this.externalProjects, files); this.collectChanges(knownProjects, this.configuredProjects, files); this.collectChanges(knownProjects, this.inferredProjects, files); @@ -1139,7 +1151,7 @@ namespace ts.server { openExternalProject(proj: protocol.ExternalProject): void { const externalProject = this.findExternalProjectByProjectName(proj.projectFileName); if (externalProject) { - this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave); + this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined); return; } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 4bc7b27db18..4e5017f2946 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -76,6 +76,10 @@ namespace ts.server { return this.compilationSettings; } + useCaseSensitiveFileNames() { + return this.host.useCaseSensitiveFileNames; + } + getCancellationToken() { return this.cancellationToken; } diff --git a/src/server/project.ts b/src/server/project.ts index 8d12c418ac1..4adbc7ca059 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -26,6 +26,10 @@ namespace ts.server { return project.getRootScriptInfos().every(f => fileExtensionIsAny(f.fileName, jsOrDts)); } + export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles { + projectErrors: Diagnostic[]; + } + export abstract class Project { private rootFiles: ScriptInfo[] = []; private rootFilesMap: FileMap = createFileMap(); @@ -57,6 +61,8 @@ namespace ts.server { private typingFiles: TypingsArray; + protected projectErrors: Diagnostic[]; + constructor( readonly projectKind: ProjectKind, readonly projectService: ProjectService, @@ -87,6 +93,10 @@ namespace ts.server { this.markAsDirty(); } + getProjectErrors() { + return this.projectErrors; + } + getLanguageService(ensureSynchronized = true): LanguageService { if (ensureSynchronized) { this.updateGraph(); @@ -329,7 +339,9 @@ namespace ts.server { getScriptInfoForNormalizedPath(fileName: NormalizedPath) { const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false); - Debug.assert(!scriptInfo || scriptInfo.isAttached(this)); + if (scriptInfo && !scriptInfo.isAttached(this)) { + return Errors.ThrowProjectDoesNotContainDocument(fileName, this); + } return scriptInfo; } @@ -371,7 +383,7 @@ namespace ts.server { return false; } - getChangesSinceVersion(lastKnownVersion?: number): protocol.ProjectFiles { + getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics { this.updateGraph(); const info = { @@ -384,7 +396,7 @@ namespace ts.server { if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) { // if current structure version is the same - return info witout any changes if (this.projectStructureVersion == this.lastReportedVersion) { - return { info }; + return { info, projectErrors: this.projectErrors }; } // compute and return the difference const lastReportedFileNames = this.lastReportedFileNames; @@ -406,14 +418,14 @@ namespace ts.server { this.lastReportedFileNames = currentFiles; this.lastReportedVersion = this.projectStructureVersion; - return { info, changes: { added, removed } }; + return { info, changes: { added, removed }, projectErrors: this.projectErrors }; } else { // unknown version - return everything const projectFileNames = this.getFileNames(); this.lastReportedFileNames = arrayToMap(projectFileNames, x => x); this.lastReportedVersion = this.projectStructureVersion; - return { info, files: projectFileNames }; + return { info, files: projectFileNames, projectErrors: this.projectErrors }; } } @@ -544,6 +556,10 @@ namespace ts.server { super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); } + setProjectErrors(projectErrors: Diagnostic[]) { + this.projectErrors = projectErrors; + } + setTypingOptions(newTypingOptions: TypingOptions): void { this.typingOptions = newTypingOptions; } @@ -636,6 +652,10 @@ namespace ts.server { return this.typingOptions; } + setProjectErrors(projectErrors: Diagnostic[]) { + this.projectErrors = projectErrors; + } + setTypingOptions(newTypingOptions: TypingOptions): void { if (!newTypingOptions) { // set default typings options diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index f72e7456cbe..79fd49d747c 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -535,6 +535,10 @@ declare namespace ts.server.protocol { changes?: ProjectChanges; } + export interface ProjectFilesWithDiagnostics extends ProjectFiles { + projectErrors: DiagnosticWithLinePosition[]; + } + export interface ChangedOpenFile { fileName: string; changes: ts.TextChange[]; diff --git a/src/server/session.ts b/src/server/session.ts index 978cfe79a48..1fdd75c037f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1261,7 +1261,21 @@ namespace ts.server { }, [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects); - return this.requiredResponse(result); + if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) { + return this.requiredResponse(result); + } + const converted = map(result, p => { + if (!p.projectErrors || p.projectErrors.length === 0) { + return p; + } + return { + info: p.info, + changes: p.changes, + files: p.files, + projectErrors: this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) + }; + }); + return this.requiredResponse(converted); }, [CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => { this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles, request.arguments.closedFiles); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index d483acdf31b..a7a0160ea62 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -64,6 +64,9 @@ namespace ts.server { export function ThrowProjectLanguageServiceDisabled(): never { throw new Error("The project's language service is disabled."); } + export function ThrowProjectDoesNotContainDocument(fileName: string, project: Project): never { + throw new Error(`Project '${project.getProjectName()}' does not contain document '${fileName}'`); + } } export function getDefaultFormatCodeSettings(host: ServerHost): FormatCodeSettings { diff --git a/src/services/services.ts b/src/services/services.ts index 6d81cc4dc86..7fdb8edc248 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3053,7 +3053,7 @@ namespace ts { let program: Program; let lastProjectVersion: string; - const useCaseSensitivefileNames = false; + const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(); const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken()); const currentDirectory = host.getCurrentDirectory();