mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 11:35:42 -06:00
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:
parent
b9794134e3
commit
640af3f75e
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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[];
|
||||
|
||||
17
tests/baselines/reference/api/typescript.d.ts
vendored
17
tests/baselines/reference/api/typescript.d.ts
vendored
@ -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;
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
[96mtests/cases/compiler/index.ts[0m:[93m3[0m:[93m8[0m - [91merror[0m[90m TS2345: [0mArgument of type '{ default: () => void; }' is not assignable to parameter of type '() => void'.
|
||||
Type '{ default: () => void; }' provides no match for the signature '(): void'.
|
||||
|
||||
[30;47m3[0m invoke(foo);
|
||||
[30;47m [0m [91m ~~~[0m
|
||||
|
||||
[96mtests/cases/compiler/index.ts[0m:[93m1[0m:[93m1[0m
|
||||
[30;47m1[0m import * as foo from "./foo";
|
||||
[30;47m [0m [96m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~[0m
|
||||
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'.
|
||||
|
||||
@ -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);
|
||||
@ -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))
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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);
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -14,5 +14,5 @@ verify.codeFix({
|
||||
description: `Use synthetic 'default' member.`,
|
||||
newFileContent: `import * as foo from "./foo";
|
||||
foo.default();`,
|
||||
index: 4,
|
||||
index: 2,
|
||||
});
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user