store project errors on project so they can be reported later

This commit is contained in:
Vladimir Matveev
2016-08-26 14:37:49 -07:00
parent 3953d6ba96
commit 8075a0dd72
9 changed files with 303 additions and 40 deletions
+35 -23
View File
@@ -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<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typingOptions: TypingOptions): Diagnostic[] {
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, 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<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean) {
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
const oldRootScriptInfos = project.getRootScriptInfos();
const newRootScriptInfos: ScriptInfo[] = [];
const newRootScriptInfoMap: NormalizedPathMap<ScriptInfo> = createNormalizedPathMap<ScriptInfo>();
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);
(<ExternalProject | ConfiguredProject>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;
}
+4
View File
@@ -76,6 +76,10 @@ namespace ts.server {
return this.compilationSettings;
}
useCaseSensitiveFileNames() {
return this.host.useCaseSensitiveFileNames;
}
getCancellationToken() {
return this.cancellationToken;
}
+25 -5
View File
@@ -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<ScriptInfo> = createFileMap<ScriptInfo>();
@@ -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
+4
View File
@@ -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[];
+15 -1
View File
@@ -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);
+3
View File
@@ -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 {