Report config file errors as part of syntactic diagnostic on the file

This commit is contained in:
Sheetal Nandi 2017-05-23 15:38:03 -07:00
parent ea60e9966d
commit 7cf93f94d4
6 changed files with 127 additions and 53 deletions

View file

@ -6,7 +6,10 @@ namespace ts.projectSystem {
describe("Project errors", () => {
function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: string[]) {
assert.isTrue(projectFiles !== undefined, "missing project files");
const errors = projectFiles.projectErrors;
checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors);
}
function checkProjectErrorsWorker(errors: Diagnostic[], expectedErrors: string[]) {
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++) {
@ -122,9 +125,13 @@ namespace ts.projectSystem {
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, [
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file.fileName, corruptedConfig.path);
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
@ -134,6 +141,8 @@ namespace ts.projectSystem {
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
});
@ -163,6 +172,8 @@ namespace ts.projectSystem {
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, []);
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
@ -171,10 +182,14 @@ namespace ts.projectSystem {
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, [
checkProjectErrors(configuredProject, []);
const projectErrors = projectService.configuredProjects[0].getAllProjectErrors();
checkProjectErrorsWorker(projectErrors, [
"'{' expected."
]);
assert.isNotNull(projectErrors[0].file);
assert.equal(projectErrors[0].file.fileName, corruptedConfig.path);
}
});
});
}
}

View file

@ -4005,11 +4005,11 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const projectName = projectService.configuredProjects[0].getProjectName();
const diags = session.executeCommand(<server.protocol.CompilerOptionsDiagnosticsRequest>{
const diags = session.executeCommand(<server.protocol.SemanticDiagnosticsSyncRequest>{
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
command: server.CommandNames.SemanticDiagnosticsSync,
seq: 2,
arguments: { projectFileName: projectName }
arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true }
}).response;
assert.isTrue(diags.length === 2);
@ -4017,18 +4017,18 @@ namespace ts.projectSystem {
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
const diagsAfterEdit = session.executeCommand(<server.protocol.CompilerOptionsDiagnosticsRequest>{
const diagsAfterEdit = session.executeCommand(<server.protocol.SemanticDiagnosticsSyncRequest>{
type: "request",
command: server.CommandNames.CompilerOptionsDiagnosticsFull,
command: server.CommandNames.SemanticDiagnosticsSync,
seq: 2,
arguments: { projectFileName: projectName }
arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true }
}).response;
assert.isTrue(diagsAfterEdit.length === 2);
verifyDiagnostic(diags[0], diagsAfterEdit[0]);
verifyDiagnostic(diags[1], diagsAfterEdit[1]);
function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePositionAndFileName, afterEditDiag: server.protocol.DiagnosticWithLinePositionAndFileName) {
function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) {
assert.equal(beforeEditDiag.message, afterEditDiag.message);
assert.equal(beforeEditDiag.code, afterEditDiag.code);
assert.equal(beforeEditDiag.category, afterEditDiag.category);
@ -4036,7 +4036,6 @@ namespace ts.projectSystem {
assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset);
assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1);
assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset);
assert.equal(beforeEditDiag.fileName, afterEditDiag.fileName);
}
});
});

View file

@ -1048,7 +1048,7 @@ namespace ts.server {
return {
success: conversionResult.success,
project,
errors: project.getProjectErrors()
errors: project.getGlobalProjectErrors()
};
}

View file

@ -221,7 +221,14 @@ namespace ts.server {
}
}
getProjectErrors() {
/**
* Get the errors that dont have any file name associated
*/
getGlobalProjectErrors() {
return filter(this.projectErrors, diagnostic => !diagnostic.file);
}
getAllProjectErrors() {
return this.projectErrors;
}
@ -399,6 +406,25 @@ namespace ts.server {
return result;
}
hasConfigFile(configFilePath: NormalizedPath) {
if (this.program && this.languageServiceEnabled) {
const configFile = this.program.getCompilerOptions().configFile;
if (configFile) {
if (configFilePath === asNormalizedPath(configFile.fileName)) {
return true;
}
if (configFile.extendedSourceFiles) {
for (const f of configFile.extendedSourceFiles) {
if (configFilePath === asNormalizedPath(f)) {
return true;
}
}
}
}
}
return false;
}
getAllEmittableFiles() {
if (!this.languageServiceEnabled) {
return [];
@ -655,7 +681,7 @@ namespace ts.server {
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
// if current structure version is the same - return info without any changes
if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) {
return { info, projectErrors: this.projectErrors };
return { info, projectErrors: this.getGlobalProjectErrors() };
}
// compute and return the difference
const lastReportedFileNames = this.lastReportedFileNames;
@ -677,14 +703,14 @@ namespace ts.server {
});
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.projectStructureVersion;
return { info, changes: { added, removed, updated }, projectErrors: this.projectErrors };
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
}
else {
// unknown version - return everything
const projectFileNames = this.getFileNames();
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
this.lastReportedVersion = this.projectStructureVersion;
return { info, files: projectFileNames, projectErrors: this.projectErrors };
return { info, files: projectFileNames, projectErrors: this.getGlobalProjectErrors() };
}
}
@ -1114,4 +1140,4 @@ namespace ts.server {
this.typeAcquisition = newTypeAcquisition;
}
}
}
}

View file

@ -358,10 +358,6 @@ namespace ts.server.protocol {
code: number;
}
export interface DiagnosticWithLinePositionAndFileName extends DiagnosticWithLinePosition {
fileName: string;
}
/**
* Response message for "projectInfo" request
*/
@ -969,7 +965,7 @@ namespace ts.server.protocol {
/**
* List of errors in project
*/
projectErrors: DiagnosticWithLinePositionAndFileName[];
projectErrors: DiagnosticWithLinePosition[];
}
/**

View file

@ -88,16 +88,16 @@ namespace ts.server {
return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 };
}
function formatConfigFileDiag(diag: ts.Diagnostic): protocol.DiagnosticWithFileName {
return {
start: diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start)),
end: diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)),
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
code: diag.code,
category: DiagnosticCategory[diag.category].toLowerCase(),
fileName: diag.file && diag.file.fileName,
source: diag.source
};
function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: false): protocol.Diagnostic;
function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
const start = diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start));
const end = diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length));
const text = ts.flattenDiagnosticMessageText(diag.messageText, "\n");
const { code, source } = diag;
const category = DiagnosticCategory[diag.category].toLowerCase();
return includeFileName ? { start, end, text, code, category, source, fileName: diag.file && diag.file.fileName } :
{ start, end, text, code, category, source };
}
export interface PendingErrorCheck {
@ -225,17 +225,6 @@ namespace ts.server {
return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`;
}
/**
* Should Remap project Files with ts diagnostics if
* - there are project errors
* - options contain configFile - so we can remove it from options before serializing
* @param p project files with ts diagnostics
*/
function shouldRemapProjectFilesWithTSDiagnostics(p: ProjectFilesWithTSDiagnostics) {
return (p.projectErrors && !!p.projectErrors.length) ||
(p.info && !!p.info.options.configFile);
}
/**
* Get the compiler options without configFile key
* @param options
@ -491,7 +480,7 @@ namespace ts.server {
}
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) {
const bakedDiags = ts.map(diagnostics, formatConfigFileDiag);
const bakedDiags = ts.map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
const ev: protocol.ConfigFileDiagnosticEvent = {
seq: 0,
type: "event",
@ -624,24 +613,57 @@ namespace ts.server {
return projectFileName && this.projectService.findProject(projectFileName);
}
private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) {
private getConfigFileAndProject(args: protocol.FileRequestArgs) {
const project = this.getProject(args.projectFileName);
return this.convertToCompilerOptionsDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics());
const file = toNormalizedPath(args.file);
return {
configFile: project && project.hasConfigFile(file) && file,
project
};
}
private convertToCompilerOptionsDiagnosticsWithLinePosition(diagnostics: Diagnostic[]) {
return diagnostics.map(d => <protocol.DiagnosticWithLinePositionAndFileName>{
private getConfigFileDiagnostics(configFile: NormalizedPath, project: Project, includeLinePosition: boolean) {
const projectErrors = project.getAllProjectErrors();
const optionsErrors = project.getLanguageService().getCompilerOptionsDiagnostics();
const diagnosticsForConfigFile = filter(
concatenate(projectErrors, optionsErrors),
diagnostic => diagnostic.file && diagnostic.file.fileName === configFile
);
return includeLinePosition ?
this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) :
map(
diagnosticsForConfigFile,
diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ false)
);
}
private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]) {
return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{
message: flattenDiagnosticMessageText(d.messageText, this.host.newLine),
start: d.start,
length: d.length,
category: DiagnosticCategory[d.category].toLowerCase(),
code: d.code,
startLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start)),
endLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start + d.length)),
fileName: d.file && d.file.fileName
endLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start + d.length))
});
}
private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) {
const project = this.getProject(args.projectFileName);
// Get diagnostics that dont have associated file with them
// The diagnostics which have file would be in config file and
// would be reported as part of configFileDiagnostics
return this.convertToDiagnosticsWithLinePosition(
filter(
project.getLanguageService().getCompilerOptionsDiagnostics(),
diagnostic => !diagnostic.file
),
/*scriptInfo*/ undefined
);
}
private convertToDiagnosticsWithLinePosition(diagnostics: Diagnostic[], scriptInfo: ScriptInfo) {
return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{
message: flattenDiagnosticMessageText(d.messageText, this.host.newLine),
@ -765,10 +787,20 @@ namespace ts.server {
}
private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
const { configFile } = this.getConfigFileAndProject(args);
if (configFile) {
// all the config file errors are reported as part of semantic check so nothing to report here
return [];
}
return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition);
}
private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
const { configFile, project } = this.getConfigFileAndProject(args);
if (configFile) {
return this.getConfigFileDiagnostics(configFile, project, args.includeLinePosition);
}
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition);
}
@ -1662,15 +1694,21 @@ namespace ts.server {
},
[CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => {
const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects);
if (!result.some(shouldRemapProjectFilesWithTSDiagnostics)) {
// Remapping of the result is needed if
// - there are project errors
// - options contain configFile - need to remove it from options before serializing
const shouldRemapProjectFilesWithTSDiagnostics = (p: ProjectFilesWithTSDiagnostics) => (p.projectErrors && !!p.projectErrors.length) || (p.info && !!p.info.options.configFile);
if (!some(result, shouldRemapProjectFilesWithTSDiagnostics)) {
return this.requiredResponse(result);
}
const converted = map(result, p => {
if (shouldRemapProjectFilesWithTSDiagnostics(p)) {
// Map the project errors
const projectErrors = p.projectErrors && p.projectErrors.length ?
this.convertToCompilerOptionsDiagnosticsWithLinePosition(p.projectErrors) :
this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) :
p.projectErrors;
// Remove the configFile in the options before serializing
const info = p.info && !!p.info.options.configFile ?
{
projectName: p.info.projectName,