Skip to content

Commit 73844ac

Browse files
committed
Accept baseline
2 parents e1e2882 + 640af3f commit 73844ac

21 files changed

+371
-142
lines changed

src/compiler/checker.ts

+23-18
Original file line numberDiff line numberDiff line change
@@ -833,11 +833,12 @@ namespace ts {
833833
return emitResolver;
834834
}
835835

836-
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
836+
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
837837
const diagnostic = location
838838
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
839839
: createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
840840
diagnostics.add(diagnostic);
841+
return diagnostic;
841842
}
842843

843844
function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) {
@@ -10491,18 +10492,21 @@ namespace ts {
1049110492
}
1049210493
}
1049310494

10494-
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo)); // TODO: GH#18217
10495-
}
10496-
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
10497-
if (headMessage && errorNode && !result && source.symbol) {
10498-
const links = getSymbolLinks(source.symbol);
10499-
if (links.originatingImport && !isImportCall(links.originatingImport)) {
10500-
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined);
10501-
if (helpfulRetry) {
10502-
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
10503-
diagnostics.add(createDiagnosticForNode(links.originatingImport, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime));
10495+
let relatedInformation: DiagnosticRelatedInformation[] | undefined;
10496+
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
10497+
if (headMessage && errorNode && !result && source.symbol) {
10498+
const links = getSymbolLinks(source.symbol);
10499+
if (links.originatingImport && !isImportCall(links.originatingImport)) {
10500+
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined);
10501+
if (helpfulRetry) {
10502+
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
10503+
const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead);
10504+
relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it
10505+
}
1050410506
}
1050510507
}
10508+
10509+
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation)); // TODO: GH#18217
1050610510
}
1050710511
return result !== Ternary.False;
1050810512

@@ -18865,14 +18869,13 @@ namespace ts {
1886518869
}
1886618870

1886718871
function invocationError(node: Node, apparentType: Type, kind: SignatureKind) {
18868-
error(node, kind === SignatureKind.Call
18872+
invocationErrorRecovery(apparentType, kind, error(node, kind === SignatureKind.Call
1886918873
? Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures
1887018874
: Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature
18871-
, typeToString(apparentType));
18872-
invocationErrorRecovery(apparentType, kind);
18875+
, typeToString(apparentType)));
1887318876
}
1887418877

18875-
function invocationErrorRecovery(apparentType: Type, kind: SignatureKind) {
18878+
function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) {
1887618879
if (!apparentType.symbol) {
1887718880
return;
1887818881
}
@@ -18882,7 +18885,8 @@ namespace ts {
1888218885
if (importNode && !isImportCall(importNode)) {
1888318886
const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind);
1888418887
if (!sigs || !sigs.length) return;
18885-
error(importNode, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime);
18888+
diagnostic.relatedInformation = diagnostic.relatedInformation || [];
18889+
diagnostic.relatedInformation.push(createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead));
1888618890
}
1888718891
}
1888818892

@@ -18961,8 +18965,9 @@ namespace ts {
1896118965
if (!callSignatures.length) {
1896218966
let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType));
1896318967
errorInfo = chainDiagnosticMessages(errorInfo, headMessage);
18964-
diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo));
18965-
invocationErrorRecovery(apparentType, SignatureKind.Call);
18968+
const diag = createDiagnosticForNodeFromMessageChain(node, errorInfo);
18969+
diagnostics.add(diag);
18970+
invocationErrorRecovery(apparentType, SignatureKind.Call, diag);
1896618971
return resolveErrorCall(node);
1896718972
}
1896818973

src/compiler/diagnosticMessages.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3828,7 +3828,7 @@
38283828
"category": "Message",
38293829
"code": 7037
38303830
},
3831-
"A namespace-style import cannot be called or constructed, and will cause a failure at runtime.": {
3831+
"Type originates at this import. A namespace-style import cannot be called or constructed, and will cause a failure at runtime. Consider using a default import or import require here instead.": {
38323832
"category": "Error",
38333833
"code": 7038
38343834
},

src/compiler/program.ts

+85-60
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ namespace ts {
252252
const gutterSeparator = " ";
253253
const resetEscapeSequence = "\u001b[0m";
254254
const ellipsis = "...";
255-
function getCategoryFormat(category: DiagnosticCategory): string {
255+
const halfIndent = " ";
256+
const indent = " ";
257+
function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences {
256258
switch (category) {
257259
case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red;
258260
case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow;
@@ -273,68 +275,79 @@ namespace ts {
273275
return s;
274276
}
275277

278+
function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) {
279+
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start);
280+
const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length);
281+
const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
282+
283+
const hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
284+
let gutterWidth = (lastLine + 1 + "").length;
285+
if (hasMoreThanFiveLines) {
286+
gutterWidth = Math.max(ellipsis.length, gutterWidth);
287+
}
288+
289+
let context = "";
290+
for (let i = firstLine; i <= lastLine; i++) {
291+
context += host.getNewLine();
292+
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
293+
// so we'll skip ahead to the second-to-last line.
294+
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
295+
context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine();
296+
i = lastLine - 1;
297+
}
298+
299+
const lineStart = getPositionOfLineAndCharacter(file, i, 0);
300+
const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
301+
let lineContent = file.text.slice(lineStart, lineEnd);
302+
lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
303+
lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
304+
305+
// Output the gutter and the actual contents of the line.
306+
context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
307+
context += lineContent + host.getNewLine();
308+
309+
// Output the gutter and the error span for the line using tildes.
310+
context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
311+
context += squiggleColor;
312+
if (i === firstLine) {
313+
// If we're on the last line, then limit it to the last character of the last line.
314+
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
315+
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
316+
317+
context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
318+
context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
319+
}
320+
else if (i === lastLine) {
321+
context += lineContent.slice(0, lastLineChar).replace(/./g, "~");
322+
}
323+
else {
324+
// Squiggle the entire line.
325+
context += lineContent.replace(/./g, "~");
326+
}
327+
context += resetEscapeSequence;
328+
}
329+
return context;
330+
}
331+
332+
function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost) {
333+
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217
334+
const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName;
335+
336+
let output = "";
337+
output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan);
338+
output += ":";
339+
output += formatColorAndReset(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow);
340+
output += ":";
341+
output += formatColorAndReset(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow);
342+
return output;
343+
}
344+
276345
export function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray<Diagnostic>, host: FormatDiagnosticsHost): string {
277346
let output = "";
278347
for (const diagnostic of diagnostics) {
279-
let context = "";
280348
if (diagnostic.file) {
281-
const { start, length, file } = diagnostic;
282-
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start!); // TODO: GH#18217
283-
const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start! + length!);
284-
const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
285-
const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName;
286-
287-
const hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
288-
let gutterWidth = (lastLine + 1 + "").length;
289-
if (hasMoreThanFiveLines) {
290-
gutterWidth = Math.max(ellipsis.length, gutterWidth);
291-
}
292-
293-
for (let i = firstLine; i <= lastLine; i++) {
294-
context += host.getNewLine();
295-
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
296-
// so we'll skip ahead to the second-to-last line.
297-
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
298-
context += formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine();
299-
i = lastLine - 1;
300-
}
301-
302-
const lineStart = getPositionOfLineAndCharacter(file, i, 0);
303-
const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
304-
let lineContent = file.text.slice(lineStart, lineEnd);
305-
lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
306-
lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
307-
308-
// Output the gutter and the actual contents of the line.
309-
context += formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
310-
context += lineContent + host.getNewLine();
311-
312-
// Output the gutter and the error span for the line using tildes.
313-
context += formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
314-
context += ForegroundColorEscapeSequences.Red;
315-
if (i === firstLine) {
316-
// If we're on the last line, then limit it to the last character of the last line.
317-
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
318-
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
319-
320-
context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
321-
context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
322-
}
323-
else if (i === lastLine) {
324-
context += lineContent.slice(0, lastLineChar).replace(/./g, "~");
325-
}
326-
else {
327-
// Squiggle the entire line.
328-
context += lineContent.replace(/./g, "~");
329-
}
330-
context += resetEscapeSequence;
331-
}
332-
333-
output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan);
334-
output += ":";
335-
output += formatColorAndReset(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow);
336-
output += ":";
337-
output += formatColorAndReset(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow);
349+
const { file, start } = diagnostic;
350+
output += formatLocation(file, start!, host); // TODO: GH#18217
338351
output += " - ";
339352
}
340353

@@ -344,7 +357,19 @@ namespace ts {
344357

345358
if (diagnostic.file) {
346359
output += host.getNewLine();
347-
output += context;
360+
output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217
361+
if (diagnostic.relatedInformation) {
362+
output += host.getNewLine();
363+
for (const { file, start, length, messageText } of diagnostic.relatedInformation) {
364+
if (file) {
365+
output += host.getNewLine();
366+
output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217
367+
output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217
368+
}
369+
output += host.getNewLine();
370+
output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine());
371+
}
372+
}
348373
}
349374

350375
output += host.getNewLine();

src/compiler/types.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -4193,16 +4193,19 @@ namespace ts {
41934193
next?: DiagnosticMessageChain;
41944194
}
41954195

4196-
export interface Diagnostic {
4197-
file: SourceFile | undefined;
4198-
start: number | undefined;
4199-
length: number | undefined;
4200-
messageText: string | DiagnosticMessageChain;
4196+
export interface Diagnostic extends DiagnosticRelatedInformation {
42014197
category: DiagnosticCategory;
42024198
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
42034199
reportsUnnecessary?: {};
42044200
code: number;
42054201
source?: string;
4202+
relatedInformation?: DiagnosticRelatedInformation[];
4203+
}
4204+
export interface DiagnosticRelatedInformation {
4205+
file: SourceFile | undefined;
4206+
start: number | undefined;
4207+
length: number | undefined;
4208+
messageText: string | DiagnosticMessageChain;
42064209
}
42074210
export interface DiagnosticWithLocation extends Diagnostic {
42084211
file: SourceFile;

src/compiler/utilities.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ namespace ts {
795795
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2, arg3);
796796
}
797797

798-
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): DiagnosticWithLocation {
798+
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation {
799799
const sourceFile = getSourceFileOfNode(node);
800800
const span = getErrorSpanForNode(sourceFile, node);
801801
return {
@@ -804,7 +804,8 @@ namespace ts {
804804
length: span.length,
805805
code: messageChain.code,
806806
category: messageChain.category,
807-
messageText: messageChain.next ? messageChain : messageChain.messageText
807+
messageText: messageChain.next ? messageChain : messageChain.messageText,
808+
relatedInformation
808809
};
809810
}
810811

src/server/protocol.ts

+23
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ namespace ts.server.protocol {
466466
code: number;
467467
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
468468
reportsUnnecessary?: {};
469+
relatedInformation?: DiagnosticRelatedInformation[];
469470
}
470471

471472
/**
@@ -2215,6 +2216,11 @@ namespace ts.server.protocol {
22152216

22162217
reportsUnnecessary?: {};
22172218

2219+
/**
2220+
* Any related spans the diagnostic may have, such as other locations relevant to an error, such as declarartion sites
2221+
*/
2222+
relatedInformation?: DiagnosticRelatedInformation[];
2223+
22182224
/**
22192225
* The error code of the diagnostic message.
22202226
*/
@@ -2233,6 +2239,23 @@ namespace ts.server.protocol {
22332239
fileName: string;
22342240
}
22352241

2242+
/**
2243+
* Represents additional spans returned with a diagnostic which are relevant to it
2244+
* Like DiagnosticWithLinePosition, this is provided in two forms:
2245+
* - start and length of the span
2246+
* - startLocation and endLocation a pair of Location objects storing the start/end line offset of the span
2247+
*/
2248+
export interface DiagnosticRelatedInformation {
2249+
/**
2250+
* Text of related or additional information.
2251+
*/
2252+
message: string;
2253+
/**
2254+
* Associated location
2255+
*/
2256+
span?: FileSpan;
2257+
}
2258+
22362259
export interface DiagnosticEventBody {
22372260
/**
22382261
* The file for which diagnostic information is reported.

0 commit comments

Comments
 (0)