* Extract function calls from a Pascal expression
(node: SyntaxNode)
| 5605 | * Extract function calls from a Pascal expression |
| 5606 | */ |
| 5607 | private extractPascalCall(node: SyntaxNode): void { |
| 5608 | if (this.nodeStack.length === 0) return; |
| 5609 | const callerId = this.nodeStack[this.nodeStack.length - 1]; |
| 5610 | if (!callerId) return; |
| 5611 | |
| 5612 | // Get the callee name — first child is typically the identifier or exprDot |
| 5613 | const firstChild = node.namedChild(0); |
| 5614 | if (!firstChild) return; |
| 5615 | |
| 5616 | let calleeName = ''; |
| 5617 | if (firstChild.type === 'exprDot') { |
| 5618 | // Chained static-factory call: `TFoo.GetInstance().DoIt()` — the exprDot's |
| 5619 | // receiver is itself an `exprCall`, so the bare identifier list would |
| 5620 | // collapse to just `DoIt` and mis-resolve to a same-named method on an |
| 5621 | // unrelated class. Encode `TFoo.GetInstance().DoIt` so resolution infers |
| 5622 | // DoIt's class from what `TFoo.GetInstance` RETURNS (#645/#608). Only a |
| 5623 | // capitalized class-factory chain; a unary outer method. |
| 5624 | const innerCall = firstChild.namedChildren.find((c: SyntaxNode) => c.type === 'exprCall'); |
| 5625 | const outerId = firstChild.namedChildren.filter((c: SyntaxNode) => c.type === 'identifier').pop(); |
| 5626 | const method = outerId ? getNodeText(outerId, this.source) : ''; |
| 5627 | if (innerCall && method && /^\w+$/.test(method)) { |
| 5628 | const innerFirst = innerCall.namedChild(0); |
| 5629 | let innerCallee = ''; |
| 5630 | if (innerFirst?.type === 'exprDot') { |
| 5631 | innerCallee = innerFirst.namedChildren |
| 5632 | .filter((c: SyntaxNode) => c.type === 'identifier') |
| 5633 | .map((id: SyntaxNode) => getNodeText(id, this.source)) |
| 5634 | .join('.'); |
| 5635 | } else if (innerFirst?.type === 'identifier') { |
| 5636 | innerCallee = getNodeText(innerFirst, this.source); |
| 5637 | } |
| 5638 | // Gate on the Delphi type-naming convention — `TFoo` classes / `IFoo` |
| 5639 | // interfaces — so a class-factory chain re-encodes but a capitalized |
| 5640 | // VARIABLE/parameter chain (Pascal capitalizes locals too: `Curve.X().Y()`, |
| 5641 | // `Self.X().Y()`) stays bare and keeps its existing bare-name resolution. |
| 5642 | calleeName = innerCallee && /^[TI][A-Z]/.test(innerCallee) |
| 5643 | ? `${innerCallee}().${method}` |
| 5644 | : method; |
| 5645 | } else { |
| 5646 | // Qualified call: Obj.Method(...) |
| 5647 | const identifiers = firstChild.namedChildren.filter( |
| 5648 | (c: SyntaxNode) => c.type === 'identifier' |
| 5649 | ); |
| 5650 | if (identifiers.length > 0) { |
| 5651 | calleeName = identifiers.map((id: SyntaxNode) => getNodeText(id, this.source)).join('.'); |
| 5652 | } |
| 5653 | } |
| 5654 | } else if (firstChild.type === 'identifier') { |
| 5655 | calleeName = getNodeText(firstChild, this.source); |
| 5656 | } |
| 5657 | |
| 5658 | if (calleeName) { |
| 5659 | this.unresolvedReferences.push({ |
| 5660 | fromNodeId: callerId, |
| 5661 | referenceName: calleeName, |
| 5662 | referenceKind: 'calls', |
| 5663 | line: node.startPosition.row + 1, |
| 5664 | column: node.startPosition.column, |
no test coverage detected