1/* @internal */ 2namespace ts.refactor.convertParamsToDestructuredObject { 3 const refactorName = "Convert parameters to destructured object"; 4 const minimumParameterLength = 2; 5 const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); 6 7 const toDestructuredAction = { 8 name: refactorName, 9 description: refactorDescription, 10 kind: "refactor.rewrite.parameters.toDestructured" 11 }; 12 registerRefactor(refactorName, { 13 kinds: [toDestructuredAction.kind], 14 getEditsForAction, 15 getAvailableActions 16 }); 17 18 function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { 19 const { file, startPosition } = context; 20 const isJSFile = isSourceFileJS(file); 21 if (isJSFile) return emptyArray; // TODO: GH#30113 22 const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); 23 if (!functionDeclaration) return emptyArray; 24 25 return [{ 26 name: refactorName, 27 description: refactorDescription, 28 actions: [toDestructuredAction] 29 }]; 30 } 31 32 function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { 33 Debug.assert(actionName === refactorName, "Unexpected action name"); 34 const { file, startPosition, program, cancellationToken, host } = context; 35 const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker()); 36 if (!functionDeclaration || !cancellationToken) return undefined; 37 38 const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken); 39 if (groupedReferences.valid) { 40 const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences)); 41 return { renameFilename: undefined, renameLocation: undefined, edits }; 42 } 43 44 return { edits: [] }; // TODO: GH#30113 45 } 46 47 function doChange( 48 sourceFile: SourceFile, 49 program: Program, 50 host: LanguageServiceHost, 51 changes: textChanges.ChangeTracker, 52 functionDeclaration: ValidFunctionDeclaration, 53 groupedReferences: GroupedReferences): void { 54 const signature = groupedReferences.signature; 55 const newFunctionDeclarationParams = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param)); 56 57 if (signature) { 58 const newSignatureParams = map(createNewParameters(signature, program, host), param => getSynthesizedDeepClone(param)); 59 replaceParameters(signature, newSignatureParams); 60 } 61 replaceParameters(functionDeclaration, newFunctionDeclarationParams); 62 63 const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos)); 64 for (const call of functionCalls) { 65 if (call.arguments && call.arguments.length) { 66 const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true); 67 changes.replaceNodeRange( 68 getSourceFileOfNode(call), 69 first(call.arguments), 70 last(call.arguments), 71 newArgument, 72 { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include }); 73 } 74 } 75 76 function replaceParameters(declarationOrSignature: ValidFunctionDeclaration | ValidMethodSignature, parameterDeclarations: ParameterDeclaration[]) { 77 changes.replaceNodeRangeWithNodes( 78 sourceFile, 79 first(declarationOrSignature.parameters), 80 last(declarationOrSignature.parameters), 81 parameterDeclarations, 82 { 83 joiner: ", ", 84 // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter 85 indentation: 0, 86 leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, 87 trailingTriviaOption: textChanges.TrailingTriviaOption.Include 88 }); 89 } 90 } 91 92 function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences { 93 const functionNames = getFunctionNames(functionDeclaration); 94 const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : []; 95 const names = deduplicate([...functionNames, ...classNames], equateValues); 96 const checker = program.getTypeChecker(); 97 98 const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken)); 99 const groupedReferences = groupReferences(references); 100 101 if (!every(groupedReferences.declarations, /*callback*/ decl => contains(names, decl))) { 102 groupedReferences.valid = false; 103 } 104 105 return groupedReferences; 106 107 function groupReferences(referenceEntries: readonly FindAllReferences.Entry[]): GroupedReferences { 108 const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] }; 109 const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true }; 110 const functionSymbols = map(functionNames, getSymbolTargetAtLocation); 111 const classSymbols = map(classNames, getSymbolTargetAtLocation); 112 const isConstructor = isConstructorDeclaration(functionDeclaration); 113 const contextualSymbols = map(functionNames, name => getSymbolForContextualType(name, checker)); 114 115 for (const entry of referenceEntries) { 116 if (entry.kind === FindAllReferences.EntryKind.Span) { 117 groupedReferences.valid = false; 118 continue; 119 } 120 121 /* Declarations in object literals may be implementations of method signatures which have a different symbol from the declaration 122 For example: 123 interface IFoo { m(a: number): void } 124 const foo: IFoo = { m(a: number): void {} } 125 In these cases we get the symbol for the signature from the contextual type. 126 */ 127 if (contains(contextualSymbols, getSymbolTargetAtLocation(entry.node))) { 128 if (isValidMethodSignature(entry.node.parent)) { 129 groupedReferences.signature = entry.node.parent; 130 continue; 131 } 132 const call = entryToFunctionCall(entry); 133 if (call) { 134 groupedReferences.functionCalls.push(call); 135 continue; 136 } 137 } 138 139 const contextualSymbol = getSymbolForContextualType(entry.node, checker); 140 if (contextualSymbol && contains(contextualSymbols, contextualSymbol)) { 141 const decl = entryToDeclaration(entry); 142 if (decl) { 143 groupedReferences.declarations.push(decl); 144 continue; 145 } 146 } 147 148 /* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function. 149 Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test: 150 class A { foo(a: number, b: number) { return a + b; } } 151 class B { foo(c: number, d: number) { return c + d; } } 152 declare const ab: A | B; 153 ab.foo(1, 2); 154 Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`. 155 When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol. 156 So we need to add a special case for this because when calling a constructor of a class through one of its subclasses, 157 the symbols are going to be different. 158 */ 159 if (contains(functionSymbols, getSymbolTargetAtLocation(entry.node)) || isNewExpressionTarget(entry.node)) { 160 const importOrExportReference = entryToImportOrExport(entry); 161 if (importOrExportReference) { 162 continue; 163 } 164 const decl = entryToDeclaration(entry); 165 if (decl) { 166 groupedReferences.declarations.push(decl); 167 continue; 168 } 169 170 const call = entryToFunctionCall(entry); 171 if (call) { 172 groupedReferences.functionCalls.push(call); 173 continue; 174 } 175 } 176 // if the refactored function is a constructor, we must also check if the references to its class are valid 177 if (isConstructor && contains(classSymbols, getSymbolTargetAtLocation(entry.node))) { 178 const importOrExportReference = entryToImportOrExport(entry); 179 if (importOrExportReference) { 180 continue; 181 } 182 183 const decl = entryToDeclaration(entry); 184 if (decl) { 185 groupedReferences.declarations.push(decl); 186 continue; 187 } 188 189 const accessExpression = entryToAccessExpression(entry); 190 if (accessExpression) { 191 classReferences.accessExpressions.push(accessExpression); 192 continue; 193 } 194 195 // Only class declarations are allowed to be used as a type (in a heritage clause), 196 // otherwise `findAllReferences` might not be able to track constructor calls. 197 if (isClassDeclaration(functionDeclaration.parent)) { 198 const type = entryToType(entry); 199 if (type) { 200 classReferences.typeUsages.push(type); 201 continue; 202 } 203 } 204 } 205 groupedReferences.valid = false; 206 } 207 208 return groupedReferences; 209 } 210 211 function getSymbolTargetAtLocation(node: Node) { 212 const symbol = checker.getSymbolAtLocation(node); 213 return symbol && getSymbolTarget(symbol, checker); 214 } 215 } 216 217 /** 218 * Gets the symbol for the contextual type of the node if it is not a union or intersection. 219 */ 220 function getSymbolForContextualType(node: Node, checker: TypeChecker): Symbol | undefined { 221 const element = getContainingObjectLiteralElement(node); 222 if (element) { 223 const contextualType = checker.getContextualTypeForObjectLiteralElement(<ObjectLiteralElementLike>element); 224 const symbol = contextualType?.getSymbol(); 225 if (symbol && !(getCheckFlags(symbol) & CheckFlags.Synthetic)) { 226 return symbol; 227 } 228 } 229 } 230 231 function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undefined { 232 const node = entry.node; 233 234 if (isImportSpecifier(node.parent) 235 || isImportClause(node.parent) 236 || isImportEqualsDeclaration(node.parent) 237 || isNamespaceImport(node.parent)) { 238 return node; 239 } 240 241 if (isExportSpecifier(node.parent) || isExportAssignment(node.parent)) { 242 return node; 243 } 244 return undefined; 245 } 246 247 function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined { 248 if (isDeclaration(entry.node.parent)) { 249 return entry.node; 250 } 251 return undefined; 252 } 253 254 function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined { 255 if (entry.node.parent) { 256 const functionReference = entry.node; 257 const parent = functionReference.parent; 258 switch (parent.kind) { 259 // foo(...) or super(...) or new Foo(...) 260 case SyntaxKind.CallExpression: 261 case SyntaxKind.NewExpression: 262 const callOrNewExpression = tryCast(parent, isCallOrNewExpression); 263 if (callOrNewExpression && callOrNewExpression.expression === functionReference) { 264 return callOrNewExpression; 265 } 266 break; 267 // x.foo(...) 268 case SyntaxKind.PropertyAccessExpression: 269 const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); 270 if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) { 271 const callOrNewExpression = tryCast(propertyAccessExpression.parent, isCallOrNewExpression); 272 if (callOrNewExpression && callOrNewExpression.expression === propertyAccessExpression) { 273 return callOrNewExpression; 274 } 275 } 276 break; 277 // x["foo"](...) 278 case SyntaxKind.ElementAccessExpression: 279 const elementAccessExpression = tryCast(parent, isElementAccessExpression); 280 if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) { 281 const callOrNewExpression = tryCast(elementAccessExpression.parent, isCallOrNewExpression); 282 if (callOrNewExpression && callOrNewExpression.expression === elementAccessExpression) { 283 return callOrNewExpression; 284 } 285 } 286 break; 287 } 288 } 289 return undefined; 290 } 291 292 function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined { 293 if (entry.node.parent) { 294 const reference = entry.node; 295 const parent = reference.parent; 296 switch (parent.kind) { 297 // `C.foo` 298 case SyntaxKind.PropertyAccessExpression: 299 const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); 300 if (propertyAccessExpression && propertyAccessExpression.expression === reference) { 301 return propertyAccessExpression; 302 } 303 break; 304 // `C["foo"]` 305 case SyntaxKind.ElementAccessExpression: 306 const elementAccessExpression = tryCast(parent, isElementAccessExpression); 307 if (elementAccessExpression && elementAccessExpression.expression === reference) { 308 return elementAccessExpression; 309 } 310 break; 311 } 312 } 313 return undefined; 314 } 315 316 function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined { 317 const reference = entry.node; 318 if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) { 319 return reference; 320 } 321 return undefined; 322 } 323 324 function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined { 325 const node = getTouchingToken(file, startPosition); 326 const functionDeclaration = getContainingFunctionDeclaration(node); 327 328 // don't offer refactor on top-level JSDoc 329 if (isTopLevelJSDoc(node)) return undefined; 330 331 if (functionDeclaration 332 && isValidFunctionDeclaration(functionDeclaration, checker) 333 && rangeContainsRange(functionDeclaration, node) 334 && !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration; 335 336 return undefined; 337 } 338 339 function isTopLevelJSDoc(node: Node): boolean { 340 const containingJSDoc = findAncestor(node, isJSDocNode); 341 if (containingJSDoc) { 342 const containingNonJSDoc = findAncestor(containingJSDoc, n => !isJSDocNode(n)); 343 return !!containingNonJSDoc && isFunctionLikeDeclaration(containingNonJSDoc); 344 } 345 return false; 346 } 347 348 function isValidMethodSignature(node: Node): node is ValidMethodSignature { 349 return isMethodSignature(node) && (isInterfaceDeclaration(node.parent) || isTypeLiteralNode(node.parent)); 350 } 351 352 function isValidFunctionDeclaration( 353 functionDeclaration: FunctionLikeDeclaration, 354 checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration { 355 if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false; 356 switch (functionDeclaration.kind) { 357 case SyntaxKind.FunctionDeclaration: 358 return hasNameOrDefault(functionDeclaration) && isSingleImplementation(functionDeclaration, checker); 359 case SyntaxKind.MethodDeclaration: 360 if (isObjectLiteralExpression(functionDeclaration.parent)) { 361 const contextualSymbol = getSymbolForContextualType(functionDeclaration.name, checker); 362 // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change 363 return contextualSymbol?.declarations.length === 1 && isSingleImplementation(functionDeclaration, checker); 364 } 365 return isSingleImplementation(functionDeclaration, checker); 366 case SyntaxKind.Constructor: 367 if (isClassDeclaration(functionDeclaration.parent)) { 368 return hasNameOrDefault(functionDeclaration.parent) && isSingleImplementation(functionDeclaration, checker); 369 } 370 else { 371 return isValidVariableDeclaration(functionDeclaration.parent.parent) 372 && isSingleImplementation(functionDeclaration, checker); 373 } 374 case SyntaxKind.FunctionExpression: 375 case SyntaxKind.ArrowFunction: 376 return isValidVariableDeclaration(functionDeclaration.parent); 377 } 378 return false; 379 } 380 381 function isSingleImplementation(functionDeclaration: FunctionLikeDeclaration, checker: TypeChecker): boolean { 382 return !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); 383 } 384 385 function hasNameOrDefault(functionOrClassDeclaration: FunctionDeclaration | ClassDeclaration): boolean { 386 if (!functionOrClassDeclaration.name) { 387 const defaultKeyword = findModifier(functionOrClassDeclaration, SyntaxKind.DefaultKeyword); 388 return !!defaultKeyword; 389 } 390 return true; 391 } 392 393 function isValidParameterNodeArray( 394 parameters: NodeArray<ParameterDeclaration>, 395 checker: TypeChecker): parameters is ValidParameterNodeArray { 396 return getRefactorableParametersLength(parameters) >= minimumParameterLength 397 && every(parameters, /*callback*/ paramDecl => isValidParameterDeclaration(paramDecl, checker)); 398 } 399 400 function isValidParameterDeclaration( 401 parameterDeclaration: ParameterDeclaration, 402 checker: TypeChecker): parameterDeclaration is ValidParameterDeclaration { 403 if (isRestParameter(parameterDeclaration)) { 404 const type = checker.getTypeAtLocation(parameterDeclaration); 405 if (!checker.isArrayType(type) && !checker.isTupleType(type)) return false; 406 } 407 return !parameterDeclaration.modifiers && !parameterDeclaration.decorators && isIdentifier(parameterDeclaration.name); 408 } 409 410 function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration { 411 return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113 412 } 413 414 function hasThisParameter(parameters: NodeArray<ParameterDeclaration>): boolean { 415 return parameters.length > 0 && isThis(parameters[0].name); 416 } 417 418 function getRefactorableParametersLength(parameters: NodeArray<ParameterDeclaration>): number { 419 if (hasThisParameter(parameters)) { 420 return parameters.length - 1; 421 } 422 return parameters.length; 423 } 424 425 function getRefactorableParameters(parameters: NodeArray<ValidParameterDeclaration>): NodeArray<ValidParameterDeclaration> { 426 if (hasThisParameter(parameters)) { 427 parameters = factory.createNodeArray(parameters.slice(1), parameters.hasTrailingComma); 428 } 429 return parameters; 430 } 431 432 function createPropertyOrShorthandAssignment(name: string, initializer: Expression): PropertyAssignment | ShorthandPropertyAssignment { 433 if (isIdentifier(initializer) && getTextOfIdentifierOrLiteral(initializer) === name) { 434 return factory.createShorthandPropertyAssignment(name); 435 } 436 return factory.createPropertyAssignment(name, initializer); 437 } 438 439 function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray<Expression>): ObjectLiteralExpression { 440 const parameters = getRefactorableParameters(functionDeclaration.parameters); 441 const hasRestParameter = isRestParameter(last(parameters)); 442 const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments; 443 const properties = map(nonRestArguments, (arg, i) => { 444 const parameterName = getParameterName(parameters[i]); 445 const property = createPropertyOrShorthandAssignment(parameterName, arg); 446 447 suppressLeadingAndTrailingTrivia(property.name); 448 if (isPropertyAssignment(property)) suppressLeadingAndTrailingTrivia(property.initializer); 449 copyComments(arg, property); 450 return property; 451 }); 452 453 if (hasRestParameter && functionArguments.length >= parameters.length) { 454 const restArguments = functionArguments.slice(parameters.length - 1); 455 const restProperty = factory.createPropertyAssignment(getParameterName(last(parameters)), factory.createArrayLiteralExpression(restArguments)); 456 properties.push(restProperty); 457 } 458 459 const objectLiteral = factory.createObjectLiteralExpression(properties, /*multiLine*/ false); 460 return objectLiteral; 461 } 462 463 function createNewParameters(functionDeclaration: ValidFunctionDeclaration | ValidMethodSignature, program: Program, host: LanguageServiceHost): NodeArray<ParameterDeclaration> { 464 const checker = program.getTypeChecker(); 465 const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters); 466 const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration); 467 const objectParameterName = factory.createObjectBindingPattern(bindingElements); 468 const objectParameterType = createParameterTypeNode(refactorableParameters); 469 470 let objectInitializer: Expression | undefined; 471 // If every parameter in the original function was optional, add an empty object initializer to the new object parameter 472 if (every(refactorableParameters, isOptionalParameter)) { 473 objectInitializer = factory.createObjectLiteralExpression(); 474 } 475 476 const objectParameter = factory.createParameterDeclaration( 477 /*decorators*/ undefined, 478 /*modifiers*/ undefined, 479 /*dotDotDotToken*/ undefined, 480 objectParameterName, 481 /*questionToken*/ undefined, 482 objectParameterType, 483 objectInitializer); 484 485 if (hasThisParameter(functionDeclaration.parameters)) { 486 const thisParameter = functionDeclaration.parameters[0]; 487 const newThisParameter = factory.createParameterDeclaration( 488 /*decorators*/ undefined, 489 /*modifiers*/ undefined, 490 /*dotDotDotToken*/ undefined, 491 thisParameter.name, 492 /*questionToken*/ undefined, 493 thisParameter.type); 494 495 suppressLeadingAndTrailingTrivia(newThisParameter.name); 496 copyComments(thisParameter.name, newThisParameter.name); 497 if (thisParameter.type) { 498 suppressLeadingAndTrailingTrivia(newThisParameter.type!); 499 copyComments(thisParameter.type, newThisParameter.type!); 500 } 501 502 return factory.createNodeArray([newThisParameter, objectParameter]); 503 } 504 return factory.createNodeArray([objectParameter]); 505 506 function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement { 507 const element = factory.createBindingElement( 508 /*dotDotDotToken*/ undefined, 509 /*propertyName*/ undefined, 510 getParameterName(parameterDeclaration), 511 isRestParameter(parameterDeclaration) && isOptionalParameter(parameterDeclaration) ? factory.createArrayLiteralExpression() : parameterDeclaration.initializer); 512 513 suppressLeadingAndTrailingTrivia(element); 514 if (parameterDeclaration.initializer && element.initializer) { 515 copyComments(parameterDeclaration.initializer, element.initializer); 516 } 517 return element; 518 } 519 520 function createParameterTypeNode(parameters: NodeArray<ValidParameterDeclaration>): TypeLiteralNode { 521 const members = map(parameters, createPropertySignatureFromParameterDeclaration); 522 const typeNode = addEmitFlags(factory.createTypeLiteralNode(members), EmitFlags.SingleLine); 523 return typeNode; 524 } 525 526 function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature { 527 let parameterType = parameterDeclaration.type; 528 if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) { 529 parameterType = getTypeNode(parameterDeclaration); 530 } 531 532 const propertySignature = factory.createPropertySignature( 533 /*modifiers*/ undefined, 534 getParameterName(parameterDeclaration), 535 isOptionalParameter(parameterDeclaration) ? factory.createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken, 536 parameterType); 537 538 suppressLeadingAndTrailingTrivia(propertySignature); 539 copyComments(parameterDeclaration.name, propertySignature.name); 540 if (parameterDeclaration.type && propertySignature.type) { 541 copyComments(parameterDeclaration.type, propertySignature.type); 542 } 543 544 return propertySignature; 545 } 546 547 function getTypeNode(node: Node): TypeNode | undefined { 548 const type = checker.getTypeAtLocation(node); 549 return getTypeNodeIfAccessible(type, node, program, host); 550 } 551 552 function isOptionalParameter(parameterDeclaration: ValidParameterDeclaration): boolean { 553 if (isRestParameter(parameterDeclaration)) { 554 const type = checker.getTypeAtLocation(parameterDeclaration); 555 return !checker.isTupleType(type); 556 } 557 return checker.isOptionalParameter(parameterDeclaration); 558 } 559 } 560 561 function getParameterName(paramDeclaration: ValidParameterDeclaration) { 562 return getTextOfIdentifierOrLiteral(paramDeclaration.name); 563 } 564 565 function getClassNames(constructorDeclaration: ValidConstructor): (Identifier | Modifier)[] { 566 switch (constructorDeclaration.parent.kind) { 567 case SyntaxKind.ClassDeclaration: 568 const classDeclaration = constructorDeclaration.parent; 569 if (classDeclaration.name) return [classDeclaration.name]; 570 // If the class declaration doesn't have a name, it should have a default modifier. 571 // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` 572 const defaultModifier = Debug.checkDefined( 573 findModifier(classDeclaration, SyntaxKind.DefaultKeyword), 574 "Nameless class declaration should be a default export"); 575 return [defaultModifier]; 576 case SyntaxKind.ClassExpression: 577 const classExpression = constructorDeclaration.parent; 578 const variableDeclaration = constructorDeclaration.parent.parent; 579 const className = classExpression.name; 580 if (className) return [className, variableDeclaration.name]; 581 return [variableDeclaration.name]; 582 } 583 } 584 585 function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] { 586 switch (functionDeclaration.kind) { 587 case SyntaxKind.FunctionDeclaration: 588 if (functionDeclaration.name) return [functionDeclaration.name]; 589 // If the function declaration doesn't have a name, it should have a default modifier. 590 // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` 591 const defaultModifier = Debug.checkDefined( 592 findModifier(functionDeclaration, SyntaxKind.DefaultKeyword), 593 "Nameless function declaration should be a default export"); 594 return [defaultModifier]; 595 case SyntaxKind.MethodDeclaration: 596 return [functionDeclaration.name]; 597 case SyntaxKind.Constructor: 598 const ctrKeyword = Debug.checkDefined( 599 findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile()), 600 "Constructor declaration should have constructor keyword"); 601 if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) { 602 const variableDeclaration = functionDeclaration.parent.parent; 603 return [variableDeclaration.name, ctrKeyword]; 604 } 605 return [ctrKeyword]; 606 case SyntaxKind.ArrowFunction: 607 return [functionDeclaration.parent.name]; 608 case SyntaxKind.FunctionExpression: 609 if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name]; 610 return [functionDeclaration.parent.name]; 611 default: 612 return Debug.assertNever(functionDeclaration, `Unexpected function declaration kind ${(functionDeclaration as ValidFunctionDeclaration).kind}`); 613 } 614 } 615 616 type ValidParameterNodeArray = NodeArray<ValidParameterDeclaration>; 617 618 interface ValidVariableDeclaration extends VariableDeclaration { 619 name: Identifier; 620 type: undefined; 621 } 622 623 interface ValidConstructor extends ConstructorDeclaration { 624 parent: ClassDeclaration | (ClassExpression & { parent: ValidVariableDeclaration }); 625 parameters: NodeArray<ValidParameterDeclaration>; 626 body: FunctionBody; 627 } 628 629 interface ValidFunction extends FunctionDeclaration { 630 parameters: NodeArray<ValidParameterDeclaration>; 631 body: FunctionBody; 632 } 633 634 interface ValidMethod extends MethodDeclaration { 635 parameters: NodeArray<ValidParameterDeclaration>; 636 body: FunctionBody; 637 } 638 639 interface ValidFunctionExpression extends FunctionExpression { 640 parent: ValidVariableDeclaration; 641 parameters: NodeArray<ValidParameterDeclaration>; 642 } 643 644 interface ValidArrowFunction extends ArrowFunction { 645 parent: ValidVariableDeclaration; 646 parameters: NodeArray<ValidParameterDeclaration>; 647 } 648 649 interface ValidMethodSignature extends MethodSignature { 650 parameters: NodeArray<ValidParameterDeclaration>; 651 } 652 653 type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression; 654 655 interface ValidParameterDeclaration extends ParameterDeclaration { 656 name: Identifier; 657 modifiers: undefined; 658 decorators: undefined; 659 } 660 661 interface GroupedReferences { 662 functionCalls: (CallExpression | NewExpression)[]; 663 declarations: Node[]; 664 signature?: ValidMethodSignature; 665 classReferences?: ClassReferences; 666 valid: boolean; 667 } 668 interface ClassReferences { 669 accessExpressions: Node[]; 670 typeUsages: Node[]; 671 } 672} 673