Do not include global errors in semantic errors from the file (#37545)

Fixes #37084
This commit is contained in:
Sheetal Nandi
2020-03-24 09:53:27 -07:00
committed by GitHub
parent 7ade67da09
commit 9f296ce96d
4 changed files with 251 additions and 208 deletions

View File

@@ -856,7 +856,7 @@ namespace ts.server {
private semanticCheck(file: NormalizedPath, project: Project) {
const diags = isDeclarationFileInJSOnlyNonConfiguredProject(project, file)
? emptyArray
: project.getLanguageService().getSemanticDiagnostics(file);
: project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file);
this.sendDiagnosticsEvent(file, project, diags, "semanticDiag");
}
@@ -1234,7 +1234,7 @@ namespace ts.server {
if (configFile) {
return this.getConfigFileDiagnostics(configFile, project!, !!args.includeLinePosition); // TODO: GH#18217
}
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), !!args.includeLinePosition);
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file), !!args.includeLinePosition);
}
private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] {

View File

@@ -807,4 +807,209 @@ namespace ts.projectSystem {
lineText,
};
}
export interface GetErrDiagnostics {
file: string | File;
syntax?: protocol.Diagnostic[];
semantic?: protocol.Diagnostic[];
suggestion?: protocol.Diagnostic[];
}
export interface VerifyGetErrRequestBase {
session: TestSession;
host: TestServerHost;
onErrEvent?: () => void;
existingTimeouts?: number;
}
export interface VerifyGetErrRequest extends VerifyGetErrRequestBase {
expected: readonly GetErrDiagnostics[];
}
export function verifyGetErrRequest(request: VerifyGetErrRequest) {
const { session, expected } = request;
session.clearMessages();
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrRequest>({
command: protocol.CommandTypes.Geterr,
arguments: {
delay: 0,
files: expected.map(f => filePath(f.file))
}
});
checkAllErrors({ ...request, expectedSequenceId });
}
export interface CheckAllErrors extends VerifyGetErrRequest {
expectedSequenceId: number;
}
function checkAllErrors({ expected, expectedSequenceId, ...rest }: CheckAllErrors) {
for (let i = 0; i < expected.length; i++) {
checkErrorsInFile({
...rest,
expected: expected[i],
expectedSequenceId: i === expected.length - 1 ? expectedSequenceId : undefined,
});
}
}
function filePath(file: string | File) {
return isString(file) ? file : file.path;
}
interface CheckErrorsInFile extends VerifyGetErrRequestBase {
expected: GetErrDiagnostics;
expectedSequenceId?: number;
}
function checkErrorsInFile({
session, host, onErrEvent, existingTimeouts, expectedSequenceId,
expected: { file, syntax, semantic, suggestion },
}: CheckErrorsInFile) {
onErrEvent = onErrEvent || noop;
if (existingTimeouts !== undefined) {
host.checkTimeoutQueueLength(existingTimeouts + 1);
host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1);
}
else {
host.checkTimeoutQueueLengthAndRun(1);
}
if (syntax) {
onErrEvent();
checkErrorMessage(session, "syntaxDiag", { file: filePath(file), diagnostics: syntax });
}
if (semantic) {
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
onErrEvent();
checkErrorMessage(session, "semanticDiag", { file: filePath(file), diagnostics: semantic });
}
if (suggestion) {
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
onErrEvent();
checkErrorMessage(session, "suggestionDiag", { file: filePath(file), diagnostics: suggestion });
}
if (expectedSequenceId !== undefined) {
checkCompleteEvent(session, syntax || semantic || suggestion ? 2 : 1, expectedSequenceId);
}
session.clearMessages();
}
function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyGetErrScenario) {
it("verifies the errors in open file", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
verifyGetErrRequest({ session, host, expected: expectedGetErr() });
});
}
function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyGetErrScenario) {
it("verifies the errors in projects", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
session.clearMessages();
for (const expected of expectedGetErrForProject()) {
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrForProjectRequest>({
command: protocol.CommandTypes.GeterrForProject,
arguments: {
delay: 0,
file: expected.project
}
});
checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId });
}
});
}
function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyGetErrScenario) {
it("verifies the errors using sync commands", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host);
openFilesForSession(openFiles(), session);
for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) {
const actualSyntax = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
arguments: {
file: filePath(file),
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSyntax, syntax, `Syntax diagnostics for file: ${filePath(file)}, project: ${project}`);
const actualSemantic = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SemanticDiagnosticsSync,
arguments: {
file: filePath(file),
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSemantic, semantic, `Semantic diagnostics for file: ${filePath(file)}, project: ${project}`);
const actualSuggestion = session.executeCommandSeq<protocol.SuggestionDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SuggestionDiagnosticsSync,
arguments: {
file: filePath(file),
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSuggestion, suggestion, `Suggestion diagnostics for file: ${filePath(file)}, project: ${project}`);
}
});
}
function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyGetErrScenario) {
it("verify config file errors", () => {
const host = createServerHost([...allFiles(), libFile]);
const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent);
for (const file of openFiles()) {
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: { file: file.path }
});
}
assert.deepEqual(events, expectedConfigFileDiagEvents().map(data => ({
eventName: server.ConfigFileDiagEvent,
data
})));
});
}
export interface GetErrForProjectDiagnostics {
project: string;
errors: readonly GetErrDiagnostics[];
}
export interface SyncDiagnostics extends GetErrDiagnostics {
project?: string;
}
export interface VerifyGetErrScenario {
allFiles: () => readonly File[];
openFiles: () => readonly File[];
expectedGetErr: () => readonly GetErrDiagnostics[];
expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[];
expectedSyncDiagnostics: () => readonly SyncDiagnostics[];
expectedConfigFileDiagEvents: () => readonly server.ConfigFileDiagEvent["data"][];
}
export function verifyGetErrScenario(scenario: VerifyGetErrScenario) {
verifyErrorsUsingGeterr(scenario);
verifyErrorsUsingGeterrForProject(scenario);
verifyErrorsUsingSyncMethods(scenario);
verifyConfigFileErrors(scenario);
}
export function emptyDiagnostics(file: File): GetErrDiagnostics {
return {
file,
syntax: emptyArray,
semantic: emptyArray,
suggestion: emptyArray
};
}
export function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics {
return { project, ...diagnostics };
}
}

View File

@@ -493,6 +493,48 @@ declare module '@custom/plugin' {
});
}
});
describe("when semantic error returns includes global error", () => {
const file: File = {
path: `${tscWatch.projectRoot}/ui.ts`,
content: `const x = async (_action: string) => {
};`
};
const config: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
function expectedDiagnostics(): GetErrDiagnostics {
const span = protocolTextSpanFromSubstring(file.content, `async (_action: string) => {`);
return {
file,
syntax: [],
semantic: [
createDiagnostic(span.start, span.end, Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option, [], "error"),
],
suggestion: []
};
}
verifyGetErrScenario({
allFiles: () => [libFile, file, config],
openFiles: () => [file],
expectedGetErr: () => [expectedDiagnostics()],
expectedGetErrForProject: () => [{
project: file.path,
errors: [
expectedDiagnostics(),
]
}],
expectedSyncDiagnostics: () => [
syncDiagnostics(expectedDiagnostics(), config.path),
],
expectedConfigFileDiagEvents: () => [{
triggerFile: file.path,
configFileName: config.path,
diagnostics: emptyArray
}]
});
});
});
describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => {

View File

@@ -1,212 +1,8 @@
namespace ts.projectSystem {
export interface GetErrDiagnostics {
file: string | File;
syntax?: protocol.Diagnostic[];
semantic?: protocol.Diagnostic[];
suggestion?: protocol.Diagnostic[];
}
export interface VerifyGetErrRequestBase {
session: TestSession;
host: TestServerHost;
onErrEvent?: () => void;
existingTimeouts?: number;
}
export interface VerifyGetErrRequest extends VerifyGetErrRequestBase {
expected: readonly GetErrDiagnostics[];
}
export function verifyGetErrRequest(request: VerifyGetErrRequest) {
const { session, expected } = request;
session.clearMessages();
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrRequest>({
command: protocol.CommandTypes.Geterr,
arguments: {
delay: 0,
files: expected.map(f => filePath(f.file))
}
});
checkAllErrors({ ...request, expectedSequenceId });
}
export interface CheckAllErrors extends VerifyGetErrRequest {
expectedSequenceId: number;
}
function checkAllErrors({ expected, expectedSequenceId, ...rest }: CheckAllErrors) {
for (let i = 0; i < expected.length; i++) {
checkErrorsInFile({
...rest,
expected: expected[i],
expectedSequenceId: i === expected.length - 1 ? expectedSequenceId : undefined,
});
}
}
function filePath(file: string | File) {
return isString(file) ? file : file.path;
}
interface CheckErrorsInFile extends VerifyGetErrRequestBase {
expected: GetErrDiagnostics;
expectedSequenceId?: number;
}
function checkErrorsInFile({
session, host, onErrEvent, existingTimeouts, expectedSequenceId,
expected: { file, syntax, semantic, suggestion },
}: CheckErrorsInFile) {
onErrEvent = onErrEvent || noop;
if (existingTimeouts !== undefined) {
host.checkTimeoutQueueLength(existingTimeouts + 1);
host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1);
}
else {
host.checkTimeoutQueueLengthAndRun(1);
}
if (syntax) {
onErrEvent();
checkErrorMessage(session, "syntaxDiag", { file: filePath(file), diagnostics: syntax });
}
if (semantic) {
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
onErrEvent();
checkErrorMessage(session, "semanticDiag", { file: filePath(file), diagnostics: semantic });
}
if (suggestion) {
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
onErrEvent();
checkErrorMessage(session, "suggestionDiag", { file: filePath(file), diagnostics: suggestion });
}
if (expectedSequenceId !== undefined) {
checkCompleteEvent(session, syntax || semantic || suggestion ? 2 : 1, expectedSequenceId);
}
session.clearMessages();
}
describe("unittests:: tsserver:: with project references and error reporting", () => {
const dependecyLocation = `${tscWatch.projectRoot}/dependency`;
const usageLocation = `${tscWatch.projectRoot}/usage`;
function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyScenario) {
it("verifies the errors in open file", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
verifyGetErrRequest({ session, host, expected: expectedGetErr() });
});
}
function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyScenario) {
it("verifies the errors in projects", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
session.clearMessages();
for (const expected of expectedGetErrForProject()) {
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrForProjectRequest>({
command: protocol.CommandTypes.GeterrForProject,
arguments: {
delay: 0,
file: expected.project
}
});
checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId });
}
});
}
function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyScenario) {
it("verifies the errors using sync commands", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host);
openFilesForSession(openFiles(), session);
for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) {
const actualSyntax = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
arguments: {
file: filePath(file),
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSyntax, syntax, `Syntax diagnostics for file: ${filePath(file)}, project: ${project}`);
const actualSemantic = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SemanticDiagnosticsSync,
arguments: {
file: filePath(file),
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSemantic, semantic, `Semantic diagnostics for file: ${filePath(file)}, project: ${project}`);
const actualSuggestion = session.executeCommandSeq<protocol.SuggestionDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SuggestionDiagnosticsSync,
arguments: {
file: filePath(file),
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSuggestion, suggestion, `Suggestion diagnostics for file: ${filePath(file)}, project: ${project}`);
}
});
}
function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyScenario) {
it("verify config file errors", () => {
const host = createServerHost([...allFiles(), libFile]);
const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent);
for (const file of openFiles()) {
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: { file: file.path }
});
}
assert.deepEqual(events, expectedConfigFileDiagEvents().map(data => ({
eventName: server.ConfigFileDiagEvent,
data
})));
});
}
interface GetErrForProjectDiagnostics {
project: string;
errors: readonly GetErrDiagnostics[];
}
interface SyncDiagnostics extends GetErrDiagnostics {
project?: string;
}
interface VerifyScenario {
allFiles: () => readonly File[];
openFiles: () => readonly File[];
expectedGetErr: () => readonly GetErrDiagnostics[];
expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[];
expectedSyncDiagnostics: () => readonly SyncDiagnostics[];
expectedConfigFileDiagEvents: () => readonly server.ConfigFileDiagEvent["data"][];
}
function verifyScenario(scenario: VerifyScenario) {
verifyErrorsUsingGeterr(scenario);
verifyErrorsUsingGeterrForProject(scenario);
verifyErrorsUsingSyncMethods(scenario);
verifyConfigFileErrors(scenario);
}
function emptyDiagnostics(file: File): GetErrDiagnostics {
return {
file,
syntax: emptyArray,
semantic: emptyArray,
suggestion: emptyArray
};
}
function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics {
return { project, ...diagnostics };
}
interface VerifyUsageAndDependency {
allFiles: readonly [File, File, File, File]; // dependencyTs, dependencyConfig, usageTs, usageConfig
@@ -252,7 +48,7 @@ namespace ts.projectSystem {
}
describe("when dependency project is not open", () => {
verifyScenario({
verifyGetErrScenario({
allFiles: () => allFiles,
openFiles: () => [usageTs],
expectedGetErr: () => [
@@ -283,7 +79,7 @@ namespace ts.projectSystem {
});
describe("when the depedency file is open", () => {
verifyScenario({
verifyGetErrScenario({
allFiles: () => allFiles,
openFiles: () => [usageTs, dependencyTs],
expectedGetErr: () => [