• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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