|
| 1 | +/* @internal */ |
| 2 | +namespace ts.codefix { |
| 3 | + type ContextualTrackChangesFunction = (cb: (changeTracker: textChanges.ChangeTracker) => void) => FileTextChanges[]; |
| 4 | + const fixId = "addMissingAsync"; |
| 5 | + const errorCodes = [ |
| 6 | + Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code, |
| 7 | + Diagnostics.Type_0_is_not_assignable_to_type_1.code, |
| 8 | + Diagnostics.Type_0_is_not_comparable_to_type_1.code |
| 9 | + ]; |
| 10 | + |
| 11 | + registerCodeFix({ |
| 12 | + fixIds: [fixId], |
| 13 | + errorCodes, |
| 14 | + getCodeActions: context => { |
| 15 | + const { sourceFile, errorCode, cancellationToken, program, span } = context; |
| 16 | + const diagnostic = find(program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile, cancellationToken), getIsMatchingAsyncError(span, errorCode)); |
| 17 | + const directSpan = diagnostic && diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined; |
| 18 | + |
| 19 | + const decl = getFixableErrorSpanDeclaration(sourceFile, directSpan); |
| 20 | + if (!decl) { |
| 21 | + return; |
| 22 | + } |
| 23 | + |
| 24 | + const trackChanges: ContextualTrackChangesFunction = cb => textChanges.ChangeTracker.with(context, cb); |
| 25 | + return [getFix(context, decl, trackChanges)]; |
| 26 | + }, |
| 27 | + getAllCodeActions: context => { |
| 28 | + const { sourceFile } = context; |
| 29 | + const fixedDeclarations = createMap<true>(); |
| 30 | + return codeFixAll(context, errorCodes, (t, diagnostic) => { |
| 31 | + const span = diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined; |
| 32 | + const decl = getFixableErrorSpanDeclaration(sourceFile, span); |
| 33 | + if (!decl) { |
| 34 | + return; |
| 35 | + } |
| 36 | + const trackChanges: ContextualTrackChangesFunction = cb => (cb(t), []); |
| 37 | + return getFix(context, decl, trackChanges, fixedDeclarations); |
| 38 | + }); |
| 39 | + }, |
| 40 | + }); |
| 41 | + |
| 42 | + type FixableDeclaration = ArrowFunction | FunctionDeclaration | FunctionExpression | MethodDeclaration; |
| 43 | + function getFix(context: CodeFixContext | CodeFixAllContext, decl: FixableDeclaration, trackChanges: ContextualTrackChangesFunction, fixedDeclarations?: Map<true>) { |
| 44 | + const changes = trackChanges(t => makeChange(t, context.sourceFile, decl, fixedDeclarations)); |
| 45 | + return createCodeFixAction(fixId, changes, Diagnostics.Add_async_modifier_to_containing_function, fixId, Diagnostics.Add_all_missing_async_modifiers); |
| 46 | + } |
| 47 | + |
| 48 | + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, insertionSite: FixableDeclaration, fixedDeclarations?: Map<true>) { |
| 49 | + if (fixedDeclarations) { |
| 50 | + if (fixedDeclarations.has(getNodeId(insertionSite).toString())) { |
| 51 | + return; |
| 52 | + } |
| 53 | + } |
| 54 | + fixedDeclarations?.set(getNodeId(insertionSite).toString(), true); |
| 55 | + const cloneWithModifier = getSynthesizedDeepClone(insertionSite, /*includeTrivia*/ true); |
| 56 | + cloneWithModifier.modifiers = createNodeArray(createModifiersFromModifierFlags(getModifierFlags(insertionSite) | ModifierFlags.Async)); |
| 57 | + cloneWithModifier.modifierFlagsCache = 0; |
| 58 | + changeTracker.replaceNode( |
| 59 | + sourceFile, |
| 60 | + insertionSite, |
| 61 | + cloneWithModifier); |
| 62 | + } |
| 63 | + |
| 64 | + function getFixableErrorSpanDeclaration(sourceFile: SourceFile, span: TextSpan | undefined): FixableDeclaration | undefined { |
| 65 | + if (!span) return undefined; |
| 66 | + const token = getTokenAtPosition(sourceFile, span.start); |
| 67 | + // Checker has already done work to determine that async might be possible, and has attached |
| 68 | + // related info to the node, so start by finding the signature that exactly matches up |
| 69 | + // with the diagnostic range. |
| 70 | + const decl = findAncestor(token, node => { |
| 71 | + if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { |
| 72 | + return "quit"; |
| 73 | + } |
| 74 | + return (isArrowFunction(node) || isMethodDeclaration(node) || isFunctionExpression(node) || isFunctionDeclaration(node)) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); |
| 75 | + }) as FixableDeclaration | undefined; |
| 76 | + |
| 77 | + return decl; |
| 78 | + } |
| 79 | + |
| 80 | + function getIsMatchingAsyncError(span: TextSpan, errorCode: number) { |
| 81 | + return ({ start, length, relatedInformation, code }: Diagnostic) => |
| 82 | + isNumber(start) && isNumber(length) && textSpansEqual({ start, length }, span) && |
| 83 | + code === errorCode && |
| 84 | + !!relatedInformation && |
| 85 | + some(relatedInformation, related => related.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code); |
| 86 | + } |
| 87 | +} |
0 commit comments