1/* @internal */ 2namespace ts.codefix { 3 const fixMissingMember = "fixMissingMember"; 4 const fixMissingProperties = "fixMissingProperties"; 5 const fixMissingAttributes = "fixMissingAttributes"; 6 const fixMissingFunctionDeclaration = "fixMissingFunctionDeclaration"; 7 8 const errorCodes = [ 9 Diagnostics.Property_0_does_not_exist_on_type_1.code, 10 Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code, 11 Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2.code, 12 Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2.code, 13 Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.code, 14 Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code, 15 Diagnostics.Cannot_find_name_0.code 16 ]; 17 18 enum InfoKind { 19 TypeLikeDeclaration, 20 Enum, 21 Function, 22 ObjectLiteral, 23 JsxAttributes, 24 Signature, 25 } 26 27 registerCodeFix({ 28 errorCodes, 29 getCodeActions(context) { 30 const typeChecker = context.program.getTypeChecker(); 31 const info = getInfo(context.sourceFile, context.span.start, context.errorCode, typeChecker, context.program); 32 if (!info) { 33 return undefined; 34 } 35 if (info.kind === InfoKind.ObjectLiteral) { 36 const changes = textChanges.ChangeTracker.with(context, t => addObjectLiteralProperties(t, context, info)); 37 return [createCodeFixAction(fixMissingProperties, changes, Diagnostics.Add_missing_properties, fixMissingProperties, Diagnostics.Add_all_missing_properties)]; 38 } 39 if (info.kind === InfoKind.JsxAttributes) { 40 const changes = textChanges.ChangeTracker.with(context, t => addJsxAttributes(t, context, info)); 41 return [createCodeFixAction(fixMissingAttributes, changes, Diagnostics.Add_missing_attributes, fixMissingAttributes, Diagnostics.Add_all_missing_attributes)]; 42 } 43 if (info.kind === InfoKind.Function || info.kind === InfoKind.Signature) { 44 const changes = textChanges.ChangeTracker.with(context, t => addFunctionDeclaration(t, context, info)); 45 return [createCodeFixAction(fixMissingFunctionDeclaration, changes, [Diagnostics.Add_missing_function_declaration_0, info.token.text], fixMissingFunctionDeclaration, Diagnostics.Add_all_missing_function_declarations)]; 46 } 47 if (info.kind === InfoKind.Enum) { 48 const changes = textChanges.ChangeTracker.with(context, t => addEnumMemberDeclaration(t, context.program.getTypeChecker(), info)); 49 return [createCodeFixAction(fixMissingMember, changes, [Diagnostics.Add_missing_enum_member_0, info.token.text], fixMissingMember, Diagnostics.Add_all_missing_members)]; 50 } 51 return concatenate(getActionsForMissingMethodDeclaration(context, info), getActionsForMissingMemberDeclaration(context, info)); 52 }, 53 fixIds: [fixMissingMember, fixMissingFunctionDeclaration, fixMissingProperties, fixMissingAttributes], 54 getAllCodeActions: context => { 55 const { program, fixId } = context; 56 const checker = program.getTypeChecker(); 57 const seen = new Map<string, true>(); 58 const typeDeclToMembers = new Map<ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, TypeLikeDeclarationInfo[]>(); 59 60 return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { 61 eachDiagnostic(context, errorCodes, diag => { 62 const info = getInfo(diag.file, diag.start, diag.code, checker, context.program); 63 if (!info || !addToSeen(seen, getNodeId(info.parentDeclaration) + "#" + info.token.text)) { 64 return; 65 } 66 if (fixId === fixMissingFunctionDeclaration && (info.kind === InfoKind.Function || info.kind === InfoKind.Signature)) { 67 addFunctionDeclaration(changes, context, info); 68 } 69 else if (fixId === fixMissingProperties && info.kind === InfoKind.ObjectLiteral) { 70 addObjectLiteralProperties(changes, context, info); 71 } 72 else if (fixId === fixMissingAttributes && info.kind === InfoKind.JsxAttributes) { 73 addJsxAttributes(changes, context, info); 74 } 75 else { 76 if (info.kind === InfoKind.Enum) { 77 addEnumMemberDeclaration(changes, checker, info); 78 } 79 if (info.kind === InfoKind.TypeLikeDeclaration) { 80 const { parentDeclaration, token } = info; 81 const infos = getOrUpdate(typeDeclToMembers, parentDeclaration, () => []); 82 if (!infos.some(i => i.token.text === token.text)) { 83 infos.push(info); 84 } 85 } 86 } 87 }); 88 89 typeDeclToMembers.forEach((infos, declaration) => { 90 const supers = isTypeLiteralNode(declaration) ? undefined : getAllSupers(declaration, checker); 91 for (const info of infos) { 92 // If some superclass added this property, don't add it again. 93 if (supers?.some(superClassOrInterface => { 94 const superInfos = typeDeclToMembers.get(superClassOrInterface); 95 return !!superInfos && superInfos.some(({ token }) => token.text === info.token.text); 96 })) continue; 97 98 const { parentDeclaration, declSourceFile, modifierFlags, token, call, isJSFile } = info; 99 // Always prefer to add a method declaration if possible. 100 if (call && !isPrivateIdentifier(token)) { 101 addMethodDeclaration(context, changes, call, token, modifierFlags & ModifierFlags.Static, parentDeclaration, declSourceFile); 102 } 103 else { 104 if (isJSFile && !isInterfaceDeclaration(parentDeclaration) && !isTypeLiteralNode(parentDeclaration)) { 105 addMissingMemberInJs(changes, declSourceFile, parentDeclaration, token, !!(modifierFlags & ModifierFlags.Static)); 106 } 107 else { 108 const typeNode = getTypeNode(checker, parentDeclaration, token); 109 addPropertyDeclaration(changes, declSourceFile, parentDeclaration, token.text, typeNode, modifierFlags & ModifierFlags.Static); 110 } 111 } 112 } 113 }); 114 })); 115 }, 116 }); 117 118 type Info = TypeLikeDeclarationInfo | EnumInfo | FunctionInfo | ObjectLiteralInfo | JsxAttributesInfo | SignatureInfo; 119 120 interface EnumInfo { 121 readonly kind: InfoKind.Enum; 122 readonly token: Identifier; 123 readonly parentDeclaration: EnumDeclaration; 124 } 125 126 interface TypeLikeDeclarationInfo { 127 readonly kind: InfoKind.TypeLikeDeclaration; 128 readonly call: CallExpression | undefined; 129 readonly token: Identifier | PrivateIdentifier; 130 readonly modifierFlags: ModifierFlags; 131 readonly parentDeclaration: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; 132 readonly declSourceFile: SourceFile; 133 readonly isJSFile: boolean; 134 } 135 136 interface FunctionInfo { 137 readonly kind: InfoKind.Function; 138 readonly call: CallExpression; 139 readonly token: Identifier; 140 readonly sourceFile: SourceFile; 141 readonly modifierFlags: ModifierFlags; 142 readonly parentDeclaration: SourceFile | ModuleDeclaration | ReturnStatement; 143 } 144 145 interface ObjectLiteralInfo { 146 readonly kind: InfoKind.ObjectLiteral; 147 readonly token: Identifier; 148 readonly properties: Symbol[]; 149 readonly parentDeclaration: ObjectLiteralExpression; 150 readonly indentation?: number; 151 } 152 153 interface JsxAttributesInfo { 154 readonly kind: InfoKind.JsxAttributes; 155 readonly token: Identifier; 156 readonly attributes: Symbol[]; 157 readonly parentDeclaration: JsxOpeningLikeElement; 158 } 159 160 interface SignatureInfo { 161 readonly kind: InfoKind.Signature; 162 readonly token: Identifier; 163 readonly signature: Signature; 164 readonly sourceFile: SourceFile; 165 readonly parentDeclaration: Node; 166 } 167 168 function getInfo(sourceFile: SourceFile, tokenPos: number, errorCode: number, checker: TypeChecker, program: Program): Info | undefined { 169 // The identifier of the missing property. eg: 170 // this.missing = 1; 171 // ^^^^^^^ 172 const token = getTokenAtPosition(sourceFile, tokenPos); 173 const parent = token.parent; 174 175 if (errorCode === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code) { 176 if (!(token.kind === SyntaxKind.OpenBraceToken && isObjectLiteralExpression(parent) && isCallExpression(parent.parent))) return undefined; 177 178 const argIndex = findIndex(parent.parent.arguments, arg => arg === parent); 179 if (argIndex < 0) return undefined; 180 181 const signature = checker.getResolvedSignature(parent.parent); 182 if (!(signature && signature.declaration && signature.parameters[argIndex])) return undefined; 183 184 const param = signature.parameters[argIndex].valueDeclaration; 185 if (!(param && isParameter(param) && isIdentifier(param.name))) return undefined; 186 187 const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent), checker.getParameterType(signature, argIndex), /* requireOptionalProperties */ false, /* matchDiscriminantProperties */ false)); 188 if (!length(properties)) return undefined; 189 return { kind: InfoKind.ObjectLiteral, token: param.name, properties, parentDeclaration: parent }; 190 } 191 192 if (!isMemberName(token)) return undefined; 193 194 if (isIdentifier(token) && hasInitializer(parent) && parent.initializer && isObjectLiteralExpression(parent.initializer)) { 195 const properties = arrayFrom(checker.getUnmatchedProperties(checker.getTypeAtLocation(parent.initializer), checker.getTypeAtLocation(token), /* requireOptionalProperties */ false, /* matchDiscriminantProperties */ false)); 196 if (!length(properties)) return undefined; 197 198 return { kind: InfoKind.ObjectLiteral, token, properties, parentDeclaration: parent.initializer }; 199 } 200 201 if (isIdentifier(token) && isJsxOpeningLikeElement(token.parent)) { 202 const target = getEmitScriptTarget(program.getCompilerOptions()); 203 const attributes = getUnmatchedAttributes(checker, target, token.parent); 204 if (!length(attributes)) return undefined; 205 return { kind: InfoKind.JsxAttributes, token, attributes, parentDeclaration: token.parent }; 206 } 207 208 if (isIdentifier(token)) { 209 const type = checker.getContextualType(token); 210 if (type && getObjectFlags(type) & ObjectFlags.Anonymous) { 211 const signature = firstOrUndefined(checker.getSignaturesOfType(type, SignatureKind.Call)); 212 if (signature === undefined) return undefined; 213 return { kind: InfoKind.Signature, token, signature, sourceFile, parentDeclaration: findScope(token) }; 214 } 215 if (isCallExpression(parent) && parent.expression === token) { 216 return { kind: InfoKind.Function, token, call: parent, sourceFile, modifierFlags: ModifierFlags.None, parentDeclaration: findScope(token) }; 217 } 218 } 219 220 if (!isPropertyAccessExpression(parent)) return undefined; 221 222 const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression)); 223 const symbol = leftExpressionType.symbol; 224 if (!symbol || !symbol.declarations) return undefined; 225 226 if (isIdentifier(token) && isCallExpression(parent.parent)) { 227 const moduleDeclaration = find(symbol.declarations, isModuleDeclaration); 228 const moduleDeclarationSourceFile = moduleDeclaration?.getSourceFile(); 229 if (moduleDeclaration && moduleDeclarationSourceFile && !isSourceFileFromLibrary(program, moduleDeclarationSourceFile)) { 230 return { kind: InfoKind.Function, token, call: parent.parent, sourceFile, modifierFlags: ModifierFlags.Export, parentDeclaration: moduleDeclaration }; 231 } 232 233 const moduleSourceFile = find(symbol.declarations, isSourceFile); 234 if (sourceFile.commonJsModuleIndicator) return undefined; 235 236 if (moduleSourceFile && !isSourceFileFromLibrary(program, moduleSourceFile)) { 237 return { kind: InfoKind.Function, token, call: parent.parent, sourceFile: moduleSourceFile, modifierFlags: ModifierFlags.Export, parentDeclaration: moduleSourceFile }; 238 } 239 } 240 241 const classDeclaration = find(symbol.declarations, isClassLike); 242 // Don't suggest adding private identifiers to anything other than a class. 243 if (!classDeclaration && isPrivateIdentifier(token)) return undefined; 244 245 // Prefer to change the class instead of the interface if they are merged 246 const declaration = classDeclaration || find(symbol.declarations, d => isInterfaceDeclaration(d) || isTypeLiteralNode(d)) as InterfaceDeclaration | TypeLiteralNode | undefined; 247 if (declaration && !isSourceFileFromLibrary(program, declaration.getSourceFile())) { 248 const makeStatic = !isTypeLiteralNode(declaration) && ((leftExpressionType as TypeReference).target || leftExpressionType) !== checker.getDeclaredTypeOfSymbol(symbol); 249 if (makeStatic && (isPrivateIdentifier(token) || isInterfaceDeclaration(declaration))) return undefined; 250 251 const declSourceFile = declaration.getSourceFile(); 252 const modifierFlags = isTypeLiteralNode(declaration) ? ModifierFlags.None : 253 (makeStatic ? ModifierFlags.Static : ModifierFlags.None) | (startsWithUnderscore(token.text) ? ModifierFlags.Private : ModifierFlags.None); 254 const isJSFile = isSourceFileJS(declSourceFile); 255 const call = tryCast(parent.parent, isCallExpression); 256 return { kind: InfoKind.TypeLikeDeclaration, token, call, modifierFlags, parentDeclaration: declaration, declSourceFile, isJSFile }; 257 } 258 259 const enumDeclaration = find(symbol.declarations, isEnumDeclaration); 260 if (enumDeclaration && !(leftExpressionType.flags & TypeFlags.EnumLike) && !isPrivateIdentifier(token) && !isSourceFileFromLibrary(program, enumDeclaration.getSourceFile())) { 261 return { kind: InfoKind.Enum, token, parentDeclaration: enumDeclaration }; 262 } 263 264 return undefined; 265 } 266 267 function getActionsForMissingMemberDeclaration(context: CodeFixContext, info: TypeLikeDeclarationInfo): CodeFixAction[] | undefined { 268 return info.isJSFile ? singleElementArray(createActionForAddMissingMemberInJavascriptFile(context, info)) : 269 createActionsForAddMissingMemberInTypeScriptFile(context, info); 270 } 271 272 function createActionForAddMissingMemberInJavascriptFile(context: CodeFixContext, { parentDeclaration, declSourceFile, modifierFlags, token }: TypeLikeDeclarationInfo): CodeFixAction | undefined { 273 if (isInterfaceDeclaration(parentDeclaration) || isTypeLiteralNode(parentDeclaration)) { 274 return undefined; 275 } 276 277 const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, parentDeclaration, token, !!(modifierFlags & ModifierFlags.Static))); 278 if (changes.length === 0) { 279 return undefined; 280 } 281 282 const diagnostic = modifierFlags & ModifierFlags.Static ? Diagnostics.Initialize_static_property_0 : 283 isPrivateIdentifier(token) ? Diagnostics.Declare_a_private_field_named_0 : Diagnostics.Initialize_property_0_in_the_constructor; 284 285 return createCodeFixAction(fixMissingMember, changes, [diagnostic, token.text], fixMissingMember, Diagnostics.Add_all_missing_members); 286 } 287 288 function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier | PrivateIdentifier, makeStatic: boolean): void { 289 const tokenName = token.text; 290 if (makeStatic) { 291 if (classDeclaration.kind === SyntaxKind.ClassExpression) { 292 return; 293 } 294 const className = classDeclaration.name!.getText(); 295 const staticInitialization = initializePropertyToUndefined(factory.createIdentifier(className), tokenName); 296 changeTracker.insertNodeAfter(sourceFile, classDeclaration, staticInitialization); 297 } 298 else if (isPrivateIdentifier(token)) { 299 const property = factory.createPropertyDeclaration( 300 /*modifiers*/ undefined, 301 tokenName, 302 /*questionToken*/ undefined, 303 /*type*/ undefined, 304 /*initializer*/ undefined); 305 306 const lastProp = getNodeToInsertPropertyAfter(classDeclaration); 307 if (lastProp) { 308 changeTracker.insertNodeAfter(sourceFile, lastProp, property); 309 } 310 else { 311 changeTracker.insertMemberAtStart(sourceFile, classDeclaration, property); 312 } 313 } 314 else { 315 const classConstructor = getFirstConstructorWithBody(classDeclaration); 316 if (!classConstructor) { 317 return; 318 } 319 const propertyInitialization = initializePropertyToUndefined(factory.createThis(), tokenName); 320 changeTracker.insertNodeAtConstructorEnd(sourceFile, classConstructor, propertyInitialization); 321 } 322 } 323 324 function initializePropertyToUndefined(obj: Expression, propertyName: string) { 325 return factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(obj, propertyName), createUndefined())); 326 } 327 328 function createActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, { parentDeclaration, declSourceFile, modifierFlags, token }: TypeLikeDeclarationInfo): CodeFixAction[] | undefined { 329 const memberName = token.text; 330 const isStatic = modifierFlags & ModifierFlags.Static; 331 const typeNode = getTypeNode(context.program.getTypeChecker(), parentDeclaration, token); 332 const addPropertyDeclarationChanges = (modifierFlags: ModifierFlags) => textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, declSourceFile, parentDeclaration, memberName, typeNode, modifierFlags)); 333 334 const actions = [createCodeFixAction(fixMissingMember, addPropertyDeclarationChanges(modifierFlags & ModifierFlags.Static), [isStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0, memberName], fixMissingMember, Diagnostics.Add_all_missing_members)]; 335 if (isStatic || isPrivateIdentifier(token)) { 336 return actions; 337 } 338 339 if (modifierFlags & ModifierFlags.Private) { 340 actions.unshift(createCodeFixActionWithoutFixAll(fixMissingMember, addPropertyDeclarationChanges(ModifierFlags.Private), [Diagnostics.Declare_private_property_0, memberName])); 341 } 342 343 actions.push(createAddIndexSignatureAction(context, declSourceFile, parentDeclaration, token.text, typeNode)); 344 return actions; 345 } 346 347 function getTypeNode(checker: TypeChecker, node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, token: Node) { 348 let typeNode: TypeNode | undefined; 349 if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { 350 const binaryExpression = token.parent.parent as BinaryExpression; 351 const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left; 352 const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression))); 353 typeNode = checker.typeToTypeNode(widenedType, node, NodeBuilderFlags.NoTruncation); 354 } 355 else { 356 const contextualType = checker.getContextualType(token.parent as Expression); 357 typeNode = contextualType ? checker.typeToTypeNode(contextualType, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.NoTruncation) : undefined; 358 } 359 return typeNode || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); 360 } 361 362 function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, tokenName: string, typeNode: TypeNode, modifierFlags: ModifierFlags): void { 363 const modifiers = modifierFlags ? factory.createNodeArray(factory.createModifiersFromModifierFlags(modifierFlags)) : undefined; 364 365 const property = isClassLike(node) 366 ? factory.createPropertyDeclaration(modifiers, tokenName, /*questionToken*/ undefined, typeNode, /*initializer*/ undefined) 367 : factory.createPropertySignature(/*modifiers*/ undefined, tokenName, /*questionToken*/ undefined, typeNode); 368 369 const lastProp = getNodeToInsertPropertyAfter(node); 370 if (lastProp) { 371 changeTracker.insertNodeAfter(sourceFile, lastProp, property); 372 } 373 else { 374 changeTracker.insertMemberAtStart(sourceFile, node, property); 375 } 376 } 377 378 // Gets the last of the first run of PropertyDeclarations, or undefined if the class does not start with a PropertyDeclaration. 379 function getNodeToInsertPropertyAfter(node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode): PropertyDeclaration | undefined { 380 let res: PropertyDeclaration | undefined; 381 for (const member of node.members) { 382 if (!isPropertyDeclaration(member)) break; 383 res = member; 384 } 385 return res; 386 } 387 388 function createAddIndexSignatureAction(context: CodeFixContext, sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, tokenName: string, typeNode: TypeNode): CodeFixAction { 389 // Index signatures cannot have the static modifier. 390 const stringTypeNode = factory.createKeywordTypeNode(SyntaxKind.StringKeyword); 391 const indexingParameter = factory.createParameterDeclaration( 392 /*modifiers*/ undefined, 393 /*dotDotDotToken*/ undefined, 394 "x", 395 /*questionToken*/ undefined, 396 stringTypeNode, 397 /*initializer*/ undefined); 398 const indexSignature = factory.createIndexSignature( 399 /*modifiers*/ undefined, 400 [indexingParameter], 401 typeNode); 402 403 const changes = textChanges.ChangeTracker.with(context, t => t.insertMemberAtStart(sourceFile, node, indexSignature)); 404 // No fixId here because code-fix-all currently only works on adding individual named properties. 405 return createCodeFixActionWithoutFixAll(fixMissingMember, changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]); 406 } 407 408 function getActionsForMissingMethodDeclaration(context: CodeFixContext, info: TypeLikeDeclarationInfo): CodeFixAction[] | undefined { 409 const { parentDeclaration, declSourceFile, modifierFlags, token, call } = info; 410 if (call === undefined) { 411 return undefined; 412 } 413 414 // Private methods are not implemented yet. 415 if (isPrivateIdentifier(token)) { 416 return undefined; 417 } 418 419 const methodName = token.text; 420 const addMethodDeclarationChanges = (modifierFlags: ModifierFlags) => textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, call, token, modifierFlags, parentDeclaration, declSourceFile)); 421 const actions = [createCodeFixAction(fixMissingMember, addMethodDeclarationChanges(modifierFlags & ModifierFlags.Static), [modifierFlags & ModifierFlags.Static ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, methodName], fixMissingMember, Diagnostics.Add_all_missing_members)]; 422 if (modifierFlags & ModifierFlags.Private) { 423 actions.unshift(createCodeFixActionWithoutFixAll(fixMissingMember, addMethodDeclarationChanges(ModifierFlags.Private), [Diagnostics.Declare_private_method_0, methodName])); 424 } 425 return actions; 426 } 427 428 function addMethodDeclaration( 429 context: CodeFixContextBase, 430 changes: textChanges.ChangeTracker, 431 callExpression: CallExpression, 432 name: Identifier, 433 modifierFlags: ModifierFlags, 434 parentDeclaration: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, 435 sourceFile: SourceFile, 436 ): void { 437 const importAdder = createImportAdder(sourceFile, context.program, context.preferences, context.host); 438 const kind = isClassLike(parentDeclaration) ? SyntaxKind.MethodDeclaration : SyntaxKind.MethodSignature; 439 const signatureDeclaration = createSignatureDeclarationFromCallExpression(kind, context, importAdder, callExpression, name, modifierFlags, parentDeclaration) as MethodDeclaration; 440 const containingMethodDeclaration = tryGetContainingMethodDeclaration(parentDeclaration, callExpression); 441 if (containingMethodDeclaration) { 442 changes.insertNodeAfter(sourceFile, containingMethodDeclaration, signatureDeclaration); 443 } 444 else { 445 changes.insertMemberAtStart(sourceFile, parentDeclaration, signatureDeclaration); 446 } 447 importAdder.writeFixes(changes); 448 } 449 450 function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: TypeChecker, { token, parentDeclaration }: EnumInfo) { 451 /** 452 * create initializer only literal enum that has string initializer. 453 * value of initializer is a string literal that equal to name of enum member. 454 * numeric enum or empty enum will not create initializer. 455 */ 456 const hasStringInitializer = some(parentDeclaration.members, member => { 457 const type = checker.getTypeAtLocation(member); 458 return !!(type && type.flags & TypeFlags.StringLike); 459 }); 460 461 const enumMember = factory.createEnumMember(token, hasStringInitializer ? factory.createStringLiteral(token.text) : undefined); 462 changes.replaceNode(parentDeclaration.getSourceFile(), parentDeclaration, factory.updateEnumDeclaration( 463 parentDeclaration, 464 parentDeclaration.modifiers, 465 parentDeclaration.name, 466 concatenate(parentDeclaration.members, singleElementArray(enumMember)) 467 ), { 468 leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, 469 trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude 470 }); 471 } 472 473 function addFunctionDeclaration(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: FunctionInfo | SignatureInfo) { 474 const quotePreference = getQuotePreference(context.sourceFile, context.preferences); 475 const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host); 476 const functionDeclaration = info.kind === InfoKind.Function 477 ? createSignatureDeclarationFromCallExpression(SyntaxKind.FunctionDeclaration, context, importAdder, info.call, idText(info.token), info.modifierFlags, info.parentDeclaration) 478 : createSignatureDeclarationFromSignature(SyntaxKind.FunctionDeclaration, context, quotePreference, info.signature, createStubbedBody(Diagnostics.Function_not_implemented.message, quotePreference), info.token, /*modifiers*/ undefined, /*optional*/ undefined, /*enclosingDeclaration*/ undefined, importAdder); 479 if (functionDeclaration === undefined) { 480 Debug.fail("fixMissingFunctionDeclaration codefix got unexpected error."); 481 } 482 483 isReturnStatement(info.parentDeclaration) 484 ? changes.insertNodeBefore(info.sourceFile, info.parentDeclaration, functionDeclaration, /*blankLineBetween*/ true) 485 : changes.insertNodeAtEndOfScope(info.sourceFile, info.parentDeclaration, functionDeclaration); 486 importAdder.writeFixes(changes); 487 } 488 489 function addJsxAttributes(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: JsxAttributesInfo) { 490 const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host); 491 const quotePreference = getQuotePreference(context.sourceFile, context.preferences); 492 const checker = context.program.getTypeChecker(); 493 const jsxAttributesNode = info.parentDeclaration.attributes; 494 const hasSpreadAttribute = some(jsxAttributesNode.properties, isJsxSpreadAttribute); 495 const attrs = map(info.attributes, attr => { 496 const value = tryGetValueFromType(context, checker, importAdder, quotePreference, checker.getTypeOfSymbol(attr), info.parentDeclaration); 497 const name = factory.createIdentifier(attr.name); 498 const jsxAttribute = factory.createJsxAttribute(name, factory.createJsxExpression(/*dotDotDotToken*/ undefined, value)); 499 // formattingScanner requires the Identifier to have a context for scanning attributes with "-" (data-foo). 500 setParent(name, jsxAttribute); 501 return jsxAttribute; 502 }); 503 const jsxAttributes = factory.createJsxAttributes(hasSpreadAttribute ? [...attrs, ...jsxAttributesNode.properties] : [...jsxAttributesNode.properties, ...attrs]); 504 const options = { prefix: jsxAttributesNode.pos === jsxAttributesNode.end ? " " : undefined }; 505 changes.replaceNode(context.sourceFile, jsxAttributesNode, jsxAttributes, options); 506 importAdder.writeFixes(changes); 507 } 508 509 function addObjectLiteralProperties(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: ObjectLiteralInfo) { 510 const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host); 511 const quotePreference = getQuotePreference(context.sourceFile, context.preferences); 512 const target = getEmitScriptTarget(context.program.getCompilerOptions()); 513 const checker = context.program.getTypeChecker(); 514 const props = map(info.properties, prop => { 515 const initializer = tryGetValueFromType(context, checker, importAdder, quotePreference, checker.getTypeOfSymbol(prop), info.parentDeclaration); 516 return factory.createPropertyAssignment(createPropertyNameFromSymbol(prop, target, quotePreference, checker), initializer); 517 }); 518 const options = { 519 leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, 520 trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, 521 indentation: info.indentation 522 }; 523 changes.replaceNode(context.sourceFile, info.parentDeclaration, factory.createObjectLiteralExpression([...info.parentDeclaration.properties, ...props], /*multiLine*/ true), options); 524 importAdder.writeFixes(changes); 525 } 526 527 function tryGetValueFromType(context: CodeFixContextBase, checker: TypeChecker, importAdder: ImportAdder, quotePreference: QuotePreference, type: Type, enclosingDeclaration: Node | undefined): Expression { 528 if (type.flags & TypeFlags.AnyOrUnknown) { 529 return createUndefined(); 530 } 531 if (type.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { 532 return factory.createStringLiteral("", /* isSingleQuote */ quotePreference === QuotePreference.Single); 533 } 534 if (type.flags & TypeFlags.Number) { 535 return factory.createNumericLiteral(0); 536 } 537 if (type.flags & TypeFlags.BigInt) { 538 return factory.createBigIntLiteral("0n"); 539 } 540 if (type.flags & TypeFlags.Boolean) { 541 return factory.createFalse(); 542 } 543 if (type.flags & TypeFlags.EnumLike) { 544 const enumMember = type.symbol.exports ? firstOrUndefined(arrayFrom(type.symbol.exports.values())) : type.symbol; 545 const name = checker.symbolToExpression(type.symbol.parent ? type.symbol.parent : type.symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, /*flags*/ undefined); 546 return enumMember === undefined || name === undefined ? factory.createNumericLiteral(0) : factory.createPropertyAccessExpression(name, checker.symbolToString(enumMember)); 547 } 548 if (type.flags & TypeFlags.NumberLiteral) { 549 return factory.createNumericLiteral((type as NumberLiteralType).value); 550 } 551 if (type.flags & TypeFlags.BigIntLiteral) { 552 return factory.createBigIntLiteral((type as BigIntLiteralType).value); 553 } 554 if (type.flags & TypeFlags.StringLiteral) { 555 return factory.createStringLiteral((type as StringLiteralType).value, /* isSingleQuote */ quotePreference === QuotePreference.Single); 556 } 557 if (type.flags & TypeFlags.BooleanLiteral) { 558 return (type === checker.getFalseType() || type === checker.getFalseType(/*fresh*/ true)) ? factory.createFalse() : factory.createTrue(); 559 } 560 if (type.flags & TypeFlags.Null) { 561 return factory.createNull(); 562 } 563 if (type.flags & TypeFlags.Union) { 564 const expression = firstDefined((type as UnionType).types, t => tryGetValueFromType(context, checker, importAdder, quotePreference, t, enclosingDeclaration)); 565 return expression ?? createUndefined(); 566 } 567 if (checker.isArrayLikeType(type)) { 568 return factory.createArrayLiteralExpression(); 569 } 570 if (isObjectLiteralType(type)) { 571 const props = map(checker.getPropertiesOfType(type), prop => { 572 const initializer = prop.valueDeclaration ? tryGetValueFromType(context, checker, importAdder, quotePreference, checker.getTypeAtLocation(prop.valueDeclaration), enclosingDeclaration) : createUndefined(); 573 return factory.createPropertyAssignment(prop.name, initializer); 574 }); 575 return factory.createObjectLiteralExpression(props, /*multiLine*/ true); 576 } 577 if (getObjectFlags(type) & ObjectFlags.Anonymous) { 578 const decl = find(type.symbol.declarations || emptyArray, or(isFunctionTypeNode, isMethodSignature, isMethodDeclaration)); 579 if (decl === undefined) return createUndefined(); 580 581 const signature = checker.getSignaturesOfType(type, SignatureKind.Call); 582 if (signature === undefined) return createUndefined(); 583 584 const func = createSignatureDeclarationFromSignature(SyntaxKind.FunctionExpression, context, quotePreference, signature[0], 585 createStubbedBody(Diagnostics.Function_not_implemented.message, quotePreference), /*name*/ undefined, /*modifiers*/ undefined, /*optional*/ undefined, /*enclosingDeclaration*/ enclosingDeclaration, importAdder) as FunctionExpression | undefined; 586 return func ?? createUndefined(); 587 } 588 if (getObjectFlags(type) & ObjectFlags.Class) { 589 const classDeclaration = getClassLikeDeclarationOfSymbol(type.symbol); 590 if (classDeclaration === undefined || hasAbstractModifier(classDeclaration)) return createUndefined(); 591 592 const constructorDeclaration = getFirstConstructorWithBody(classDeclaration); 593 if (constructorDeclaration && length(constructorDeclaration.parameters)) return createUndefined(); 594 595 return factory.createNewExpression(factory.createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined); 596 } 597 return createUndefined(); 598 } 599 600 function createUndefined() { 601 return factory.createIdentifier("undefined"); 602 } 603 604 function isObjectLiteralType(type: Type) { 605 return (type.flags & TypeFlags.Object) && 606 ((getObjectFlags(type) & ObjectFlags.ObjectLiteral) || (type.symbol && tryCast(singleOrUndefined(type.symbol.declarations), isTypeLiteralNode))); 607 } 608 609 function getUnmatchedAttributes(checker: TypeChecker, target: ScriptTarget, source: JsxOpeningLikeElement) { 610 const attrsType = checker.getContextualType(source.attributes); 611 if (attrsType === undefined) return emptyArray; 612 613 const targetProps = attrsType.getProperties(); 614 if (!length(targetProps)) return emptyArray; 615 616 const seenNames = new Set<__String>(); 617 for (const sourceProp of source.attributes.properties) { 618 if (isJsxAttribute(sourceProp)) { 619 seenNames.add(sourceProp.name.escapedText); 620 } 621 if (isJsxSpreadAttribute(sourceProp)) { 622 const type = checker.getTypeAtLocation(sourceProp.expression); 623 for (const prop of type.getProperties()) { 624 seenNames.add(prop.escapedName); 625 } 626 } 627 } 628 return filter(targetProps, targetProp => 629 isIdentifierText(targetProp.name, target, LanguageVariant.JSX) && !((targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial) || seenNames.has(targetProp.escapedName))); 630 } 631 632 function tryGetContainingMethodDeclaration(node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, callExpression: CallExpression) { 633 if (isTypeLiteralNode(node)) { 634 return undefined; 635 } 636 const declaration = findAncestor(callExpression, n => isMethodDeclaration(n) || isConstructorDeclaration(n)); 637 return declaration && declaration.parent === node ? declaration : undefined; 638 } 639 640 function createPropertyNameFromSymbol(symbol: Symbol, target: ScriptTarget, quotePreference: QuotePreference, checker: TypeChecker) { 641 if (isTransientSymbol(symbol)) { 642 const prop = checker.symbolToNode(symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.WriteComputedProps); 643 if (prop && isComputedPropertyName(prop)) return prop; 644 } 645 return createPropertyNameNodeForIdentifierOrLiteral(symbol.name, target, quotePreference === QuotePreference.Single); 646 } 647 648 function findScope(node: Node) { 649 if (findAncestor(node, isJsxExpression)) { 650 const returnStatement = findAncestor(node.parent, isReturnStatement); 651 if (returnStatement) return returnStatement; 652 } 653 return getSourceFileOfNode(node); 654 } 655} 656