Report config file errors as part of syntactic diagnostic on the file
This commit is contained in:
parent
ea60e9966d
commit
7cf93f94d4
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1048,7 +1048,7 @@ namespace ts.server {
|
|||
return {
|
||||
success: conversionResult.success,
|
||||
project,
|
||||
errors: project.getProjectErrors()
|
||||
errors: project.getGlobalProjectErrors()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue