| 
 | 1 | +import {  | 
 | 2 | +    append,  | 
 | 3 | +    ArrowFunction,  | 
 | 4 | +    CodeFixAction,  | 
 | 5 | +    declarationNameToString,  | 
 | 6 | +    Diagnostics,  | 
 | 7 | +    factory,  | 
 | 8 | +    filter,  | 
 | 9 | +    findAncestor,  | 
 | 10 | +    first,  | 
 | 11 | +    forEach,  | 
 | 12 | +    FunctionDeclaration,  | 
 | 13 | +    FunctionExpression,  | 
 | 14 | +    FunctionLikeDeclaration,  | 
 | 15 | +    getNameOfAccessExpression,  | 
 | 16 | +    getNameOfDeclaration,  | 
 | 17 | +    getTokenAtPosition,  | 
 | 18 | +    isAccessExpression,  | 
 | 19 | +    isCallExpression,  | 
 | 20 | +    isIdentifier,  | 
 | 21 | +    isParameter,  | 
 | 22 | +    isPropertyDeclaration,  | 
 | 23 | +    isSourceFileFromLibrary,  | 
 | 24 | +    isVariableDeclaration,  | 
 | 25 | +    last,  | 
 | 26 | +    lastOrUndefined,  | 
 | 27 | +    length,  | 
 | 28 | +    map,  | 
 | 29 | +    MethodDeclaration,  | 
 | 30 | +    Node,  | 
 | 31 | +    NodeBuilderFlags,  | 
 | 32 | +    ParameterDeclaration,  | 
 | 33 | +    Program,  | 
 | 34 | +    QuestionToken,  | 
 | 35 | +    some,  | 
 | 36 | +    SourceFile,  | 
 | 37 | +    SyntaxKind,  | 
 | 38 | +    textChanges,  | 
 | 39 | +    Type,  | 
 | 40 | +    TypeChecker,  | 
 | 41 | +    TypeNode,  | 
 | 42 | +} from "../_namespaces/ts";  | 
 | 43 | +import {  | 
 | 44 | +    codeFixAll,  | 
 | 45 | +    createCodeFixAction,  | 
 | 46 | +    registerCodeFix,  | 
 | 47 | +} from "../_namespaces/ts.codefix";  | 
 | 48 | + | 
 | 49 | +const addMissingParamFixId = "addMissingParam";  | 
 | 50 | +const addOptionalParamFixId = "addOptionalParam";  | 
 | 51 | +const errorCodes = [Diagnostics.Expected_0_arguments_but_got_1.code];  | 
 | 52 | + | 
 | 53 | +registerCodeFix({  | 
 | 54 | +    errorCodes,  | 
 | 55 | +    fixIds: [addMissingParamFixId, addOptionalParamFixId],  | 
 | 56 | +    getCodeActions(context) {  | 
 | 57 | +        const info = getInfo(context.sourceFile, context.program, context.span.start);  | 
 | 58 | +        if (info === undefined) return undefined;  | 
 | 59 | + | 
 | 60 | +        const { name, declarations, newParameters, newOptionalParameters } = info;  | 
 | 61 | +        const actions: CodeFixAction[] = [];  | 
 | 62 | + | 
 | 63 | +        if (length(newParameters)) {  | 
 | 64 | +            append(  | 
 | 65 | +                actions,  | 
 | 66 | +                createCodeFixAction(  | 
 | 67 | +                    addMissingParamFixId,  | 
 | 68 | +                    textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, declarations, newParameters)),  | 
 | 69 | +                    [length(newParameters) > 1 ? Diagnostics.Add_missing_parameters_to_0 : Diagnostics.Add_missing_parameter_to_0, name],  | 
 | 70 | +                    addMissingParamFixId,  | 
 | 71 | +                    Diagnostics.Add_all_missing_parameters,  | 
 | 72 | +                ),  | 
 | 73 | +            );  | 
 | 74 | +        }  | 
 | 75 | + | 
 | 76 | +        if (length(newOptionalParameters)) {  | 
 | 77 | +            append(  | 
 | 78 | +                actions,  | 
 | 79 | +                createCodeFixAction(  | 
 | 80 | +                    addOptionalParamFixId,  | 
 | 81 | +                    textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, declarations, newOptionalParameters)),  | 
 | 82 | +                    [length(newOptionalParameters) > 1 ? Diagnostics.Add_optional_parameters_to_0 : Diagnostics.Add_optional_parameter_to_0, name],  | 
 | 83 | +                    addOptionalParamFixId,  | 
 | 84 | +                    Diagnostics.Add_all_optional_parameters,  | 
 | 85 | +                ),  | 
 | 86 | +            );  | 
 | 87 | +        }  | 
 | 88 | + | 
 | 89 | +        return actions;  | 
 | 90 | +    },  | 
 | 91 | +    getAllCodeActions: context =>  | 
 | 92 | +        codeFixAll(context, errorCodes, (changes, diag) => {  | 
 | 93 | +            const info = getInfo(context.sourceFile, context.program, diag.start);  | 
 | 94 | +            if (info) {  | 
 | 95 | +                const { declarations, newParameters, newOptionalParameters } = info;  | 
 | 96 | +                if (context.fixId === addMissingParamFixId) {  | 
 | 97 | +                    doChange(changes, context.sourceFile, declarations, newParameters);  | 
 | 98 | +                }  | 
 | 99 | +                if (context.fixId === addOptionalParamFixId) {  | 
 | 100 | +                    doChange(changes, context.sourceFile, declarations, newOptionalParameters);  | 
 | 101 | +                }  | 
 | 102 | +            }  | 
 | 103 | +        }),  | 
 | 104 | +});  | 
 | 105 | + | 
 | 106 | +type ConvertibleSignatureDeclaration =  | 
 | 107 | +    | FunctionDeclaration  | 
 | 108 | +    | FunctionExpression  | 
 | 109 | +    | ArrowFunction  | 
 | 110 | +    | MethodDeclaration;  | 
 | 111 | + | 
 | 112 | +interface SignatureInfo {  | 
 | 113 | +    readonly newParameters: ParameterInfo[];  | 
 | 114 | +    readonly newOptionalParameters: ParameterInfo[];  | 
 | 115 | +    readonly name: string;  | 
 | 116 | +    readonly declarations: ConvertibleSignatureDeclaration[];  | 
 | 117 | +}  | 
 | 118 | + | 
 | 119 | +interface ParameterInfo {  | 
 | 120 | +    readonly pos: number;  | 
 | 121 | +    readonly declaration: ParameterDeclaration;  | 
 | 122 | +}  | 
 | 123 | + | 
 | 124 | +function getInfo(sourceFile: SourceFile, program: Program, pos: number): SignatureInfo | undefined {  | 
 | 125 | +    const token = getTokenAtPosition(sourceFile, pos);  | 
 | 126 | +    const callExpression = findAncestor(token, isCallExpression);  | 
 | 127 | +    if (callExpression === undefined || length(callExpression.arguments) === 0) {  | 
 | 128 | +        return undefined;  | 
 | 129 | +    }  | 
 | 130 | + | 
 | 131 | +    const checker = program.getTypeChecker();  | 
 | 132 | +    const type = checker.getTypeAtLocation(callExpression.expression);  | 
 | 133 | +    const convertibleSignatureDeclarations = filter(type.symbol.declarations, isConvertibleSignatureDeclaration);  | 
 | 134 | +    if (convertibleSignatureDeclarations === undefined) {  | 
 | 135 | +        return undefined;  | 
 | 136 | +    }  | 
 | 137 | + | 
 | 138 | +    const nonOverloadDeclaration = lastOrUndefined(convertibleSignatureDeclarations);  | 
 | 139 | +    if (  | 
 | 140 | +        nonOverloadDeclaration === undefined ||  | 
 | 141 | +        nonOverloadDeclaration.body === undefined ||  | 
 | 142 | +        isSourceFileFromLibrary(program, nonOverloadDeclaration.getSourceFile())  | 
 | 143 | +    ) {  | 
 | 144 | +        return undefined;  | 
 | 145 | +    }  | 
 | 146 | + | 
 | 147 | +    const name = tryGetName(nonOverloadDeclaration);  | 
 | 148 | +    if (name === undefined) {  | 
 | 149 | +        return undefined;  | 
 | 150 | +    }  | 
 | 151 | + | 
 | 152 | +    const newParameters: ParameterInfo[] = [];  | 
 | 153 | +    const newOptionalParameters: ParameterInfo[] = [];  | 
 | 154 | +    const parametersLength = length(nonOverloadDeclaration.parameters);  | 
 | 155 | +    const argumentsLength = length(callExpression.arguments);  | 
 | 156 | +    if (parametersLength > argumentsLength) {  | 
 | 157 | +        return undefined;  | 
 | 158 | +    }  | 
 | 159 | + | 
 | 160 | +    const declarations = [nonOverloadDeclaration, ...getOverloads(nonOverloadDeclaration, convertibleSignatureDeclarations)];  | 
 | 161 | +    for (let i = 0, pos = 0, paramIndex = 0; i < argumentsLength; i++) {  | 
 | 162 | +        const arg = callExpression.arguments[i];  | 
 | 163 | +        const expr = isAccessExpression(arg) ? getNameOfAccessExpression(arg) : arg;  | 
 | 164 | +        const type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg)));  | 
 | 165 | +        const parameter = pos < parametersLength ? nonOverloadDeclaration.parameters[pos] : undefined;  | 
 | 166 | +        if (  | 
 | 167 | +            parameter &&  | 
 | 168 | +            checker.isTypeAssignableTo(type, checker.getTypeAtLocation(parameter))  | 
 | 169 | +        ) {  | 
 | 170 | +            pos++;  | 
 | 171 | +            continue;  | 
 | 172 | +        }  | 
 | 173 | + | 
 | 174 | +        const name = expr && isIdentifier(expr) ? expr.text : `p${paramIndex++}`;  | 
 | 175 | +        const typeNode = typeToTypeNode(checker, type, nonOverloadDeclaration);  | 
 | 176 | +        append(newParameters, {  | 
 | 177 | +            pos: i,  | 
 | 178 | +            declaration: createParameter(name, typeNode, /*questionToken*/ undefined),  | 
 | 179 | +        });  | 
 | 180 | + | 
 | 181 | +        if (isOptionalPos(declarations, pos)) {  | 
 | 182 | +            continue;  | 
 | 183 | +        }  | 
 | 184 | + | 
 | 185 | +        append(newOptionalParameters, {  | 
 | 186 | +            pos: i,  | 
 | 187 | +            declaration: createParameter(name, typeNode, factory.createToken(SyntaxKind.QuestionToken)),  | 
 | 188 | +        });  | 
 | 189 | +    }  | 
 | 190 | + | 
 | 191 | +    return {  | 
 | 192 | +        newParameters,  | 
 | 193 | +        newOptionalParameters,  | 
 | 194 | +        name: declarationNameToString(name),  | 
 | 195 | +        declarations,  | 
 | 196 | +    };  | 
 | 197 | +}  | 
 | 198 | + | 
 | 199 | +function tryGetName(node: FunctionLikeDeclaration) {  | 
 | 200 | +    const name = getNameOfDeclaration(node);  | 
 | 201 | +    if (name) {  | 
 | 202 | +        return name;  | 
 | 203 | +    }  | 
 | 204 | + | 
 | 205 | +    if (  | 
 | 206 | +        isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ||  | 
 | 207 | +        isPropertyDeclaration(node.parent) ||  | 
 | 208 | +        isParameter(node.parent)  | 
 | 209 | +    ) {  | 
 | 210 | +        return node.parent.name;  | 
 | 211 | +    }  | 
 | 212 | +}  | 
 | 213 | + | 
 | 214 | +function typeToTypeNode(checker: TypeChecker, type: Type, enclosingDeclaration: Node) {  | 
 | 215 | +    return checker.typeToTypeNode(checker.getWidenedType(type), enclosingDeclaration, NodeBuilderFlags.NoTruncation)  | 
 | 216 | +        ?? factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword);  | 
 | 217 | +}  | 
 | 218 | + | 
 | 219 | +function doChange(  | 
 | 220 | +    changes: textChanges.ChangeTracker,  | 
 | 221 | +    sourceFile: SourceFile,  | 
 | 222 | +    declarations: ConvertibleSignatureDeclaration[],  | 
 | 223 | +    newParameters: ParameterInfo[],  | 
 | 224 | +) {  | 
 | 225 | +    forEach(declarations, declaration => {  | 
 | 226 | +        if (length(declaration.parameters)) {  | 
 | 227 | +            changes.replaceNodeRangeWithNodes(  | 
 | 228 | +                sourceFile,  | 
 | 229 | +                first(declaration.parameters),  | 
 | 230 | +                last(declaration.parameters),  | 
 | 231 | +                updateParameters(declaration, newParameters),  | 
 | 232 | +                {  | 
 | 233 | +                    joiner: ", ",  | 
 | 234 | +                    indentation: 0,  | 
 | 235 | +                    leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll,  | 
 | 236 | +                    trailingTriviaOption: textChanges.TrailingTriviaOption.Include,  | 
 | 237 | +                },  | 
 | 238 | +            );  | 
 | 239 | +        }  | 
 | 240 | +        else {  | 
 | 241 | +            forEach(updateParameters(declaration, newParameters), (parameter, index) => {  | 
 | 242 | +                if (length(declaration.parameters) === 0 && index === 0) {  | 
 | 243 | +                    changes.insertNodeAt(sourceFile, declaration.parameters.end, parameter);  | 
 | 244 | +                }  | 
 | 245 | +                else {  | 
 | 246 | +                    changes.insertNodeAtEndOfList(sourceFile, declaration.parameters, parameter);  | 
 | 247 | +                }  | 
 | 248 | +            });  | 
 | 249 | +        }  | 
 | 250 | +    });  | 
 | 251 | +}  | 
 | 252 | + | 
 | 253 | +function isConvertibleSignatureDeclaration(node: Node): node is ConvertibleSignatureDeclaration {  | 
 | 254 | +    switch (node.kind) {  | 
 | 255 | +        case SyntaxKind.FunctionDeclaration:  | 
 | 256 | +        case SyntaxKind.FunctionExpression:  | 
 | 257 | +        case SyntaxKind.MethodDeclaration:  | 
 | 258 | +        case SyntaxKind.ArrowFunction:  | 
 | 259 | +            return true;  | 
 | 260 | +        default:  | 
 | 261 | +            return false;  | 
 | 262 | +    }  | 
 | 263 | +}  | 
 | 264 | + | 
 | 265 | +function updateParameters(node: ConvertibleSignatureDeclaration, newParameters: readonly ParameterInfo[]) {  | 
 | 266 | +    const parameters = map(node.parameters, p =>  | 
 | 267 | +        factory.createParameterDeclaration(  | 
 | 268 | +            p.modifiers,  | 
 | 269 | +            p.dotDotDotToken,  | 
 | 270 | +            p.name,  | 
 | 271 | +            p.questionToken,  | 
 | 272 | +            p.type,  | 
 | 273 | +            p.initializer,  | 
 | 274 | +        ));  | 
 | 275 | +    for (const { pos, declaration } of newParameters) {  | 
 | 276 | +        const prev = pos > 0 ? parameters[pos - 1] : undefined;  | 
 | 277 | +        parameters.splice(  | 
 | 278 | +            pos,  | 
 | 279 | +            0,  | 
 | 280 | +            factory.updateParameterDeclaration(  | 
 | 281 | +                declaration,  | 
 | 282 | +                declaration.modifiers,  | 
 | 283 | +                declaration.dotDotDotToken,  | 
 | 284 | +                declaration.name,  | 
 | 285 | +                prev && prev.questionToken ? factory.createToken(SyntaxKind.QuestionToken) : declaration.questionToken,  | 
 | 286 | +                declaration.type,  | 
 | 287 | +                declaration.initializer,  | 
 | 288 | +            ),  | 
 | 289 | +        );  | 
 | 290 | +    }  | 
 | 291 | +    return parameters;  | 
 | 292 | +}  | 
 | 293 | + | 
 | 294 | +function getOverloads(implementation: ConvertibleSignatureDeclaration, declarations: readonly ConvertibleSignatureDeclaration[]): ConvertibleSignatureDeclaration[] {  | 
 | 295 | +    const overloads: ConvertibleSignatureDeclaration[] = [];  | 
 | 296 | +    for (const declaration of declarations) {  | 
 | 297 | +        if (isOverload(declaration)) {  | 
 | 298 | +            if (length(declaration.parameters) === length(implementation.parameters)) {  | 
 | 299 | +                overloads.push(declaration);  | 
 | 300 | +                continue;  | 
 | 301 | +            }  | 
 | 302 | +            if (length(declaration.parameters) > length(implementation.parameters)) {  | 
 | 303 | +                return [];  | 
 | 304 | +            }  | 
 | 305 | +        }  | 
 | 306 | +    }  | 
 | 307 | +    return overloads;  | 
 | 308 | +}  | 
 | 309 | + | 
 | 310 | +function isOverload(declaration: ConvertibleSignatureDeclaration) {  | 
 | 311 | +    return isConvertibleSignatureDeclaration(declaration) && declaration.body === undefined;  | 
 | 312 | +}  | 
 | 313 | + | 
 | 314 | +function createParameter(name: string, type: TypeNode, questionToken: QuestionToken | undefined) {  | 
 | 315 | +    return factory.createParameterDeclaration(  | 
 | 316 | +        /*modifiers*/ undefined,  | 
 | 317 | +        /*dotDotDotToken*/ undefined,  | 
 | 318 | +        name,  | 
 | 319 | +        questionToken,  | 
 | 320 | +        type,  | 
 | 321 | +        /*initializer*/ undefined,  | 
 | 322 | +    );  | 
 | 323 | +}  | 
 | 324 | + | 
 | 325 | +function isOptionalPos(declarations: ConvertibleSignatureDeclaration[], pos: number) {  | 
 | 326 | +    return length(declarations) && some(declarations, d => pos < length(d.parameters) && !!d.parameters[pos] && d.parameters[pos].questionToken === undefined);  | 
 | 327 | +}  | 
0 commit comments