mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-29 10:09:46 -05:00
Do not include global errors in semantic errors from the file (#37545)
Fixes #37084
This commit is contained in:
@@ -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[] {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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: () => [
|
||||
|
||||
Reference in New Issue
Block a user