Improved error spans for call errors:

1. When calling a non-callable expression the error span is on the call target not on the whole call
2. When calling a method, the error for overload resolution now includes the arguments (this was previously regressed by #31414)
This commit is contained in:
Titian Cernicova-Dragomir
2019-06-28 23:23:21 +03:00
parent bc07eec015
commit e4bca9649a
90 changed files with 338 additions and 247 deletions

View File

@@ -21298,8 +21298,34 @@ namespace ts {
return Debug.fail();
}
}
function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) {
let start: number;
let length: number;
const sourceFile = getSourceFileOfNode(node);
function getArgumentArityError(node: Node, signatures: ReadonlyArray<Signature>, args: ReadonlyArray<Expression>) {
if (isPropertyAccessExpression(node.expression)) {
const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name);
start = nameSpan.start;
length = doNotIncludeArguments ? nameSpan.length : node.end - start;
}
else {
const expressionSpan = getErrorSpanForNode(sourceFile, node.expression);
start = expressionSpan.start;
length = doNotIncludeArguments ? expressionSpan.length : node.end - start;
}
return { start, length, sourceFile };
}
function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation {
if (isCallExpression(node)) {
const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node);
return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3);
}
else {
return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3);
}
}
function getArgumentArityError(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, args: ReadonlyArray<Expression>) {
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
let belowArgCount = Number.NEGATIVE_INFINITY;
@@ -21346,11 +21372,11 @@ namespace ts {
}
}
if (min < argCount && argCount < max) {
return createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
}
if (!hasSpreadArgument && argCount < min) {
const diagnostic = createDiagnosticForNode(node, error, paramRange, argCount);
const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount);
return related ? addRelatedInfo(diagnostic, related) : diagnostic;
}
@@ -21425,8 +21451,7 @@ namespace ts {
reorderCandidates(signatures, candidates);
if (!candidates.length) {
if (reportErrors) {
const errorNode = getCallErrorNode(node);
diagnostics.add(createDiagnosticForNode(errorNode, Diagnostics.Call_target_does_not_contain_any_signatures));
diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures));
}
return resolveErrorCall(node);
}
@@ -21504,13 +21529,12 @@ namespace ts {
// If candidate is undefined, it means that no candidates had a suitable arity. In that case,
// skip the checkApplicableSignature check.
if (reportErrors) {
const errorNode = getCallErrorNode(node);
if (candidateForArgumentError) {
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, CheckMode.Normal, /*reportErrors*/ true);
}
else if (candidateForArgumentArityError) {
diagnostics.add(getArgumentArityError(errorNode, [candidateForArgumentArityError], args));
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args));
}
else if (candidateForTypeArgumentError) {
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError);
@@ -21518,31 +21542,19 @@ namespace ts {
else {
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
if (signaturesWithCorrectTypeArgumentArity.length === 0) {
diagnostics.add(getTypeArgumentArityError(errorNode, signatures, typeArguments!));
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!));
}
else if (!isDecorator) {
diagnostics.add(getArgumentArityError(errorNode, signaturesWithCorrectTypeArgumentArity, args));
diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args));
}
else if (fallbackError) {
diagnostics.add(createDiagnosticForNode(errorNode, fallbackError));
diagnostics.add(getDiagnosticForCallNode(node, fallbackError));
}
}
}
return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
function getCallErrorNode(node: CallLikeExpression): Node {
if (isCallExpression(node)) {
if (isPropertyAccessExpression(node.expression)) {
return node.expression.name;
}
else {
return node.expression;
}
}
return node;
}
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
candidateForArgumentError = undefined;
candidateForArgumentArityError = undefined;
@@ -21825,7 +21837,7 @@ namespace ts {
relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.It_is_highly_likely_that_you_are_missing_a_semicolon);
}
}
invocationError(node, apparentType, SignatureKind.Call, relatedInformation);
invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation);
}
return resolveErrorCall(node);
}
@@ -21942,7 +21954,7 @@ namespace ts {
return signature;
}
invocationError(node, expressionType, SignatureKind.Construct);
invocationError(node.expression, expressionType, SignatureKind.Construct);
return resolveErrorCall(node);
}
@@ -22089,8 +22101,13 @@ namespace ts {
Diagnostics.This_expression_is_not_constructable
);
}
function invocationError(node: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) {
const diagnostic = createDiagnosticForNodeFromMessageChain(node, invocationErrorDetails(apparentType, kind));
function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) {
const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, invocationErrorDetails(apparentType, kind));
if (isCallExpression(errorTarget.parent)) {
const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true);
diagnostic.start = start;
diagnostic.length = length;
}
diagnostics.add(diagnostic);
invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic);
}
@@ -22129,7 +22146,7 @@ namespace ts {
}
if (!callSignatures.length) {
invocationError(node, apparentType, SignatureKind.Call);
invocationError(node.tag, apparentType, SignatureKind.Call);
return resolveErrorCall(node);
}
@@ -22187,7 +22204,7 @@ namespace ts {
if (!callSignatures.length) {
let errorInfo = invocationErrorDetails(apparentType, SignatureKind.Call);
errorInfo = chainDiagnosticMessages(errorInfo, headMessage);
const diag = createDiagnosticForNodeFromMessageChain(node, errorInfo);
const diag = createDiagnosticForNodeFromMessageChain(node.expression, errorInfo);
diagnostics.add(diag);
invocationErrorRecovery(apparentType, SignatureKind.Call, diag);
return resolveErrorCall(node);

View File

@@ -40,7 +40,7 @@ namespace ts.codefix {
function getActionsForUsageOfInvalidImport(context: CodeFixContext): CodeFixAction[] | undefined {
const sourceFile = context.sourceFile;
const targetKind = Diagnostics.This_expression_is_not_callable.code === context.errorCode ? SyntaxKind.CallExpression : SyntaxKind.NewExpression;
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start), a => a.kind === targetKind && a.getStart() === context.span.start && a.getEnd() === (context.span.start + context.span.length)) as CallExpression | NewExpression;
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start), a => a.kind === targetKind) as CallExpression | NewExpression;
if (!node) {
return [];
}