Introduce related spans into tsserver protocol (#24548)

* Introduce related spans into tsserver protocol

* Incorporate pretty output, implement esModuleInterop related span
This commit is contained in:
Wesley Wigham 2018-06-15 10:54:36 -07:00 committed by GitHub
parent b9794134e3
commit 640af3f75e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 371 additions and 142 deletions

View File

@ -833,11 +833,12 @@ namespace ts {
return emitResolver;
}
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
const diagnostic = location
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
: createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
diagnostics.add(diagnostic);
return diagnostic;
}
function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) {
@ -10491,18 +10492,21 @@ namespace ts {
}
}
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo)); // TODO: GH#18217
}
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
if (headMessage && errorNode && !result && source.symbol) {
const links = getSymbolLinks(source.symbol);
if (links.originatingImport && !isImportCall(links.originatingImport)) {
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined);
if (helpfulRetry) {
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
diagnostics.add(createDiagnosticForNode(links.originatingImport, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime));
let relatedInformation: DiagnosticRelatedInformation[] | undefined;
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
if (headMessage && errorNode && !result && source.symbol) {
const links = getSymbolLinks(source.symbol);
if (links.originatingImport && !isImportCall(links.originatingImport)) {
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined);
if (helpfulRetry) {
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
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);
relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it
}
}
}
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation)); // TODO: GH#18217
}
return result !== Ternary.False;
@ -18865,14 +18869,13 @@ namespace ts {
}
function invocationError(node: Node, apparentType: Type, kind: SignatureKind) {
error(node, kind === SignatureKind.Call
invocationErrorRecovery(apparentType, kind, error(node, kind === SignatureKind.Call
? Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures
: Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature
, typeToString(apparentType));
invocationErrorRecovery(apparentType, kind);
, typeToString(apparentType)));
}
function invocationErrorRecovery(apparentType: Type, kind: SignatureKind) {
function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) {
if (!apparentType.symbol) {
return;
}
@ -18882,7 +18885,8 @@ namespace ts {
if (importNode && !isImportCall(importNode)) {
const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind);
if (!sigs || !sigs.length) return;
error(importNode, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime);
diagnostic.relatedInformation = diagnostic.relatedInformation || [];
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));
}
}
@ -18961,8 +18965,9 @@ namespace ts {
if (!callSignatures.length) {
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));
errorInfo = chainDiagnosticMessages(errorInfo, headMessage);
diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo));
invocationErrorRecovery(apparentType, SignatureKind.Call);
const diag = createDiagnosticForNodeFromMessageChain(node, errorInfo);
diagnostics.add(diag);
invocationErrorRecovery(apparentType, SignatureKind.Call, diag);
return resolveErrorCall(node);
}

View File

@ -252,7 +252,9 @@ namespace ts {
const gutterSeparator = " ";
const resetEscapeSequence = "\u001b[0m";
const ellipsis = "...";
function getCategoryFormat(category: DiagnosticCategory): string {
const halfIndent = " ";
const indent = " ";
function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences {
switch (category) {
case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red;
case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow;
@ -273,68 +275,79 @@ namespace ts {
return s;
}
function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) {
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start);
const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length);
const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
const hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
let gutterWidth = (lastLine + 1 + "").length;
if (hasMoreThanFiveLines) {
gutterWidth = Math.max(ellipsis.length, gutterWidth);
}
let context = "";
for (let i = firstLine; i <= lastLine; i++) {
context += host.getNewLine();
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
// so we'll skip ahead to the second-to-last line.
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine();
i = lastLine - 1;
}
const lineStart = getPositionOfLineAndCharacter(file, i, 0);
const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
let lineContent = file.text.slice(lineStart, lineEnd);
lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
// Output the gutter and the actual contents of the line.
context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
context += lineContent + host.getNewLine();
// Output the gutter and the error span for the line using tildes.
context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
context += squiggleColor;
if (i === firstLine) {
// If we're on the last line, then limit it to the last character of the last line.
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
}
else if (i === lastLine) {
context += lineContent.slice(0, lastLineChar).replace(/./g, "~");
}
else {
// Squiggle the entire line.
context += lineContent.replace(/./g, "~");
}
context += resetEscapeSequence;
}
return context;
}
function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost) {
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217
const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName;
let output = "";
output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan);
output += ":";
output += formatColorAndReset(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow);
output += ":";
output += formatColorAndReset(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow);
return output;
}
export function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray<Diagnostic>, host: FormatDiagnosticsHost): string {
let output = "";
for (const diagnostic of diagnostics) {
let context = "";
if (diagnostic.file) {
const { start, length, file } = diagnostic;
const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start!); // TODO: GH#18217
const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start! + length!);
const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line;
const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName;
const hasMoreThanFiveLines = (lastLine - firstLine) >= 4;
let gutterWidth = (lastLine + 1 + "").length;
if (hasMoreThanFiveLines) {
gutterWidth = Math.max(ellipsis.length, gutterWidth);
}
for (let i = firstLine; i <= lastLine; i++) {
context += host.getNewLine();
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
// so we'll skip ahead to the second-to-last line.
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
context += formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine();
i = lastLine - 1;
}
const lineStart = getPositionOfLineAndCharacter(file, i, 0);
const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length;
let lineContent = file.text.slice(lineStart, lineEnd);
lineContent = lineContent.replace(/\s+$/g, ""); // trim from end
lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces
// Output the gutter and the actual contents of the line.
context += formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator;
context += lineContent + host.getNewLine();
// Output the gutter and the error span for the line using tildes.
context += formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator;
context += ForegroundColorEscapeSequences.Red;
if (i === firstLine) {
// If we're on the last line, then limit it to the last character of the last line.
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
const lastCharForLine = i === lastLine ? lastLineChar : undefined;
context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
}
else if (i === lastLine) {
context += lineContent.slice(0, lastLineChar).replace(/./g, "~");
}
else {
// Squiggle the entire line.
context += lineContent.replace(/./g, "~");
}
context += resetEscapeSequence;
}
output += formatColorAndReset(relativeFileName, ForegroundColorEscapeSequences.Cyan);
output += ":";
output += formatColorAndReset(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow);
output += ":";
output += formatColorAndReset(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow);
const { file, start } = diagnostic;
output += formatLocation(file, start!, host); // TODO: GH#18217
output += " - ";
}
@ -344,7 +357,19 @@ namespace ts {
if (diagnostic.file) {
output += host.getNewLine();
output += context;
output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217
if (diagnostic.relatedInformation) {
output += host.getNewLine();
for (const { file, start, length, messageText } of diagnostic.relatedInformation) {
if (file) {
output += host.getNewLine();
output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217
output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217
}
output += host.getNewLine();
output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine());
}
}
}
output += host.getNewLine();

View File

@ -3828,7 +3828,7 @@
"category": "Message",
"code": 7037
},
"A namespace-style import cannot be called or constructed, and will cause a failure at runtime.": {
"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.": {
"category": "Error",
"code": 7038
},

View File

@ -4193,16 +4193,19 @@ namespace ts {
next?: DiagnosticMessageChain;
}
export interface Diagnostic {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
export interface Diagnostic extends DiagnosticRelatedInformation {
category: DiagnosticCategory;
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
code: number;
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
export interface DiagnosticRelatedInformation {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
}
export interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;

View File

@ -799,7 +799,7 @@ namespace ts {
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2, arg3);
}
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): DiagnosticWithLocation {
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation {
const sourceFile = getSourceFileOfNode(node);
const span = getErrorSpanForNode(sourceFile, node);
return {
@ -808,7 +808,8 @@ namespace ts {
length: span.length,
code: messageChain.code,
category: messageChain.category,
messageText: messageChain.next ? messageChain : messageChain.messageText
messageText: messageChain.next ? messageChain : messageChain.messageText,
relatedInformation
};
}

View File

@ -466,6 +466,7 @@ namespace ts.server.protocol {
code: number;
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
relatedInformation?: DiagnosticRelatedInformation[];
}
/**
@ -2215,6 +2216,11 @@ namespace ts.server.protocol {
reportsUnnecessary?: {};
/**
* Any related spans the diagnostic may have, such as other locations relevant to an error, such as declarartion sites
*/
relatedInformation?: DiagnosticRelatedInformation[];
/**
* The error code of the diagnostic message.
*/
@ -2233,6 +2239,23 @@ namespace ts.server.protocol {
fileName: string;
}
/**
* Represents additional spans returned with a diagnostic which are relevant to it
* Like DiagnosticWithLinePosition, this is provided in two forms:
* - start and length of the span
* - startLocation and endLocation a pair of Location objects storing the start/end line offset of the span
*/
export interface DiagnosticRelatedInformation {
/**
* Text of related or additional information.
*/
message: string;
/**
* Associated location
*/
span?: FileSpan;
}
export interface DiagnosticEventBody {
/**
* The file for which diagnostic information is reported.

View File

@ -76,7 +76,24 @@ namespace ts.server {
code: diag.code,
category: diagnosticCategoryName(diag),
reportsUnnecessary: diag.reportsUnnecessary,
source: diag.source
source: diag.source,
relatedInformation: map(diag.relatedInformation, formatRelatedInformation),
};
}
function formatRelatedInformation(info: DiagnosticRelatedInformation): protocol.DiagnosticRelatedInformation {
if (!info.file) {
return {
message: flattenDiagnosticMessageText(info.messageText, "\n")
};
}
return {
span: {
start: convertToLocation(getLineAndCharacterOfPosition(info.file, info.start!)),
end: convertToLocation(getLineAndCharacterOfPosition(info.file, info.start! + info.length!)), // TODO: GH#18217
file: info.file.fileName
},
message: flattenDiagnosticMessageText(info.messageText, "\n")
};
}
@ -92,8 +109,19 @@ namespace ts.server {
const text = flattenDiagnosticMessageText(diag.messageText, "\n");
const { code, source } = diag;
const category = diagnosticCategoryName(diag);
return includeFileName ? { start, end, text, code, category, source, reportsUnnecessary: diag.reportsUnnecessary, fileName: diag.file && diag.file.fileName } :
{ start, end, text, code, category, reportsUnnecessary: diag.reportsUnnecessary, source };
const common = {
start,
end,
text,
code,
category,
reportsUnnecessary: diag.reportsUnnecessary,
source,
relatedInformation: map(diag.relatedInformation, formatRelatedInformation),
};
return includeFileName
? { ...common, fileName: diag.file && diag.file.fileName }
: common;
}
export interface PendingErrorCheck {
@ -612,7 +640,8 @@ namespace ts.server {
category: diagnosticCategoryName(d),
code: d.code,
startLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start!)))!, // TODO: GH#18217
endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))! // TODO: GH#18217
endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))!, // TODO: GH#18217
relatedInformation: map(d.relatedInformation, formatRelatedInformation)
}));
}
@ -640,7 +669,8 @@ namespace ts.server {
source: d.source,
startLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start!), // TODO: GH#18217
endLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start! + d.length!),
reportsUnnecessary: d.reportsUnnecessary
reportsUnnecessary: d.reportsUnnecessary,
relatedInformation: map(d.relatedInformation, formatRelatedInformation),
});
}

View File

@ -2,25 +2,6 @@
namespace ts.codefix {
const fixName = "invalidImportSyntax";
registerCodeFix({
errorCodes: [Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime.code],
getCodeActions: getActionsForInvalidImport
});
function getActionsForInvalidImport(context: CodeFixContext): CodeFixAction[] | undefined {
const sourceFile = context.sourceFile;
// This is the whole import statement, eg:
// import * as Bluebird from 'bluebird';
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false).parent as ImportDeclaration;
if (!isImportDeclaration(node)) {
// No import quick fix for import calls
return [];
}
return getCodeFixesForImportDeclaration(context, node);
}
function getCodeFixesForImportDeclaration(context: CodeFixContext, node: ImportDeclaration): CodeFixAction[] {
const sourceFile = getSourceFileOfNode(node);
const namespace = getNamespaceDeclarationNode(node) as NamespaceImport;
@ -45,7 +26,7 @@ namespace ts.codefix {
function createAction(context: CodeFixContext, sourceFile: SourceFile, node: Node, replacement: Node): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, node, replacement));
return createCodeFixActionNoFixId("invalidImportSyntax", changes, [Diagnostics.Replace_import_with_0, changes[0].textChanges[0].newText]);
return createCodeFixActionNoFixId(fixName, changes, [Diagnostics.Replace_import_with_0, changes[0].textChanges[0].newText]);
}
registerCodeFix({
@ -64,6 +45,38 @@ namespace ts.codefix {
return [];
}
const expr = node.expression;
return getImportCodeFixesForExpression(context, expr);
}
registerCodeFix({
errorCodes: [
// The following error codes cover pretty much all assignability errors that could involve an expression
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code,
Diagnostics.Type_0_does_not_satisfy_the_constraint_1.code,
Diagnostics.Type_0_is_not_assignable_to_type_1.code,
Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated.code,
Diagnostics.Type_predicate_0_is_not_assignable_to_1.code,
Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2.code,
Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2.code,
Diagnostics.Numeric_index_type_0_is_not_assignable_to_string_index_type_1.code,
Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2.code,
Diagnostics.Property_0_in_type_1_is_not_assignable_to_type_2.code,
Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property.code,
Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1.code,
],
getCodeActions: getActionsForInvalidImportLocation
});
function getActionsForInvalidImportLocation(context: CodeFixContext): CodeFixAction[] | undefined {
const sourceFile = context.sourceFile;
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false), a => a.getStart() === context.span.start && a.getEnd() === (context.span.start + context.span.length));
if (!node) {
return [];
}
return getImportCodeFixesForExpression(context, node);
}
function getImportCodeFixesForExpression(context: CodeFixContext, expr: Node): CodeFixAction[] | undefined {
const type = context.program.getTypeChecker().getTypeAtLocation(expr)!; // TODO: GH#18217
if (!(type.symbol && (type.symbol as TransientSymbol).originatingImport)) {
return [];
@ -73,8 +86,11 @@ namespace ts.codefix {
if (!isImportCall(relatedImport)) {
addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport));
}
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, expr, createPropertyAccess(expr, "default"), {}));
fixes.push(createCodeFixActionNoFixId(fixName, changes, Diagnostics.Use_synthetic_default_member));
if (isExpression(expr) && !(isNamedDeclaration(expr.parent) && expr.parent.name === expr)) {
const sourceFile = context.sourceFile;
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, expr, createPropertyAccess(expr, "default"), {}));
fixes.push(createCodeFixActionNoFixId(fixName, changes, Diagnostics.Use_synthetic_default_member));
}
return fixes;
}
}

View File

@ -503,8 +503,8 @@ namespace ts.projectSystem {
checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent);
}
function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}): protocol.Diagnostic {
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, source: undefined };
function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic {
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined };
}
function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void {

View File

@ -3576,16 +3576,19 @@ declare namespace ts {
code: number;
next?: DiagnosticMessageChain;
}
interface Diagnostic {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
interface Diagnostic extends DiagnosticRelatedInformation {
category: DiagnosticCategory;
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
code: number;
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
interface DiagnosticRelatedInformation {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
}
interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
@ -5762,7 +5765,7 @@ declare namespace ts {
Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0: DiagnosticMessage;
Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0: DiagnosticMessage;
Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports: DiagnosticMessage;
A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime: DiagnosticMessage;
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: DiagnosticMessage;
Mapped_object_type_implicitly_has_an_any_template_type: DiagnosticMessage;
You_cannot_rename_this_element: DiagnosticMessage;
You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library: DiagnosticMessage;
@ -6126,7 +6129,7 @@ declare namespace ts {
function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation;
function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic;
function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation;
function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): DiagnosticWithLocation;
function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation;
function getSpanOfTokenAtPosition(sourceFile: SourceFile, pos: number): TextSpan;
function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpan;
function isExternalOrCommonJsModule(file: SourceFile): boolean;
@ -12382,6 +12385,7 @@ declare namespace ts.server.protocol {
category: string;
code: number;
reportsUnnecessary?: {};
relatedInformation?: DiagnosticRelatedInformation[];
}
interface ProjectInfoResponse extends Response {
body?: ProjectInfo;
@ -12972,12 +12976,17 @@ declare namespace ts.server.protocol {
text: string;
category: string;
reportsUnnecessary?: {};
relatedInformation?: DiagnosticRelatedInformation[];
code?: number;
source?: string;
}
interface DiagnosticWithFileName extends Diagnostic {
fileName: string;
}
interface DiagnosticRelatedInformation {
message: string;
span?: FileSpan;
}
interface DiagnosticEventBody {
file: string;
diagnostics: Diagnostic[];

View File

@ -3576,16 +3576,19 @@ declare namespace ts {
code: number;
next?: DiagnosticMessageChain;
}
interface Diagnostic {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
interface Diagnostic extends DiagnosticRelatedInformation {
category: DiagnosticCategory;
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
code: number;
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
interface DiagnosticRelatedInformation {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
}
interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
@ -5762,7 +5765,7 @@ declare namespace ts {
Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0: DiagnosticMessage;
Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0: DiagnosticMessage;
Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports: DiagnosticMessage;
A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime: DiagnosticMessage;
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: DiagnosticMessage;
Mapped_object_type_implicitly_has_an_any_template_type: DiagnosticMessage;
You_cannot_rename_this_element: DiagnosticMessage;
You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library: DiagnosticMessage;
@ -6126,7 +6129,7 @@ declare namespace ts {
function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation;
function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray<Node>, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic;
function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation;
function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): DiagnosticWithLocation;
function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain, relatedInformation?: DiagnosticRelatedInformation[]): DiagnosticWithLocation;
function getSpanOfTokenAtPosition(sourceFile: SourceFile, pos: number): TextSpan;
function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpan;
function isExternalOrCommonJsModule(file: SourceFile): boolean;

View File

@ -0,0 +1,24 @@
tests/cases/compiler/index.ts:3:8 - error TS2345: Argument of type '{ default: () => void; }' is not assignable to parameter of type '() => void'.
Type '{ default: () => void; }' provides no match for the signature '(): void'.
3 invoke(foo);
   ~~~
tests/cases/compiler/index.ts:1:1
1 import * as foo from "./foo";
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
==== tests/cases/compiler/foo.d.ts (0 errors) ====
declare function foo(): void;
declare namespace foo {}
export = foo;
==== tests/cases/compiler/index.ts (1 errors) ====
import * as foo from "./foo";
function invoke(f: () => void) { f(); }
invoke(foo);
~~~
!!! error TS2345: Argument of type '{ default: () => void; }' is not assignable to parameter of type '() => void'.
!!! error TS2345: Type '{ default: () => void; }' provides no match for the signature '(): void'.

View File

@ -0,0 +1,25 @@
//// [tests/cases/compiler/esModuleInteropPrettyErrorRelatedInformation.ts] ////
//// [foo.d.ts]
declare function foo(): void;
declare namespace foo {}
export = foo;
//// [index.ts]
import * as foo from "./foo";
function invoke(f: () => void) { f(); }
invoke(foo);
//// [index.js]
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
exports.__esModule = true;
var foo = __importStar(require("./foo"));
function invoke(f) { f(); }
invoke(foo);

View File

@ -0,0 +1,23 @@
=== tests/cases/compiler/foo.d.ts ===
declare function foo(): void;
>foo : Symbol(foo, Decl(foo.d.ts, 0, 0), Decl(foo.d.ts, 0, 29))
declare namespace foo {}
>foo : Symbol(foo, Decl(foo.d.ts, 0, 0), Decl(foo.d.ts, 0, 29))
export = foo;
>foo : Symbol(foo, Decl(foo.d.ts, 0, 0), Decl(foo.d.ts, 0, 29))
=== tests/cases/compiler/index.ts ===
import * as foo from "./foo";
>foo : Symbol(foo, Decl(index.ts, 0, 6))
function invoke(f: () => void) { f(); }
>invoke : Symbol(invoke, Decl(index.ts, 0, 29))
>f : Symbol(f, Decl(index.ts, 1, 16))
>f : Symbol(f, Decl(index.ts, 1, 16))
invoke(foo);
>invoke : Symbol(invoke, Decl(index.ts, 0, 29))
>foo : Symbol(foo, Decl(index.ts, 0, 6))

View File

@ -0,0 +1,25 @@
=== tests/cases/compiler/foo.d.ts ===
declare function foo(): void;
>foo : () => void
declare namespace foo {}
>foo : () => void
export = foo;
>foo : () => void
=== tests/cases/compiler/index.ts ===
import * as foo from "./foo";
>foo : { default: () => void; }
function invoke(f: () => void) { f(); }
>invoke : (f: () => void) => void
>f : () => void
>f() : void
>f : () => void
invoke(foo);
>invoke(foo) : void
>invoke : (f: () => void) => void
>foo : { default: () => void; }

View File

@ -0,0 +1,10 @@
// @pretty: true
// @esModuleInterop: true
// @filename: foo.d.ts
declare function foo(): void;
declare namespace foo {}
export = foo;
// @filename: index.ts
import * as foo from "./foo";
function invoke(f: () => void) { f(); }
invoke(foo);

View File

@ -6,13 +6,15 @@
////export = foo;
// @Filename: index.ts
////[|import * as foo from "./foo";|]
////import * as foo from "./foo";
////function invoke(f: () => void) { f(); }
////invoke(foo);
////invoke([|foo|]);
goTo.file(1);
verify.codeFix({
description: `Replace import with 'import foo = require("./foo");'.`,
newRangeContent: `import foo = require("./foo");`,
newFileContent: `import foo = require("./foo");
function invoke(f: () => void) { f(); }
invoke(foo);`,
index: 1,
});

View File

@ -14,5 +14,5 @@ verify.codeFix({
description: `Use synthetic 'default' member.`,
newFileContent: `import * as foo from "./foo";
foo.default();`,
index: 4,
index: 2,
});

View File

@ -6,13 +6,15 @@
////export = foo;
// @Filename: index.ts
////[|import * as foo from "./foo";|]
////import * as foo from "./foo";
////function invoke(f: () => void) { f(); }
////invoke(foo);
////invoke([|foo|]);
goTo.file(1);
verify.codeFix({
description: `Replace import with 'import foo from "./foo";'.`,
newRangeContent: `import foo from "./foo";`,
newFileContent: `import foo from "./foo";
function invoke(f: () => void) { f(); }
invoke(foo);`,
index: 0,
});

View File

@ -7,13 +7,15 @@
////export = foo;
// @Filename: index.ts
////[|import * as foo from "./foo";|]
////import * as foo from "./foo";
////function invoke(f: () => void) { f(); }
////invoke(foo);
////invoke([|foo|]);
goTo.file(1);
verify.codeFix({
description: `Replace import with 'import foo from "./foo";'.`,
newRangeContent: `import foo from "./foo";`,
newFileContent: `import foo from "./foo";
function invoke(f: () => void) { f(); }
invoke(foo);`,
index: 0,
});

View File

@ -6,12 +6,13 @@
////export = foo;
// @Filename: index.ts
////[|import * as foo from "./foo";|]
////foo();
////import * as foo from "./foo";
////[|foo|]();
goTo.file(1);
verify.codeFix({
description: `Replace import with 'import foo = require("./foo");'.`,
newRangeContent: `import foo = require("./foo");`,
newFileContent: `import foo = require("./foo");
foo();`,
index: 1,
});