* Extract function calls from a Pascal expression
(node: SyntaxNode)
| 6526 | * Extract function calls from a Pascal expression |
| 6527 | */ |
| 6528 | private extractPascalCall(node: SyntaxNode): void { |
| 6529 | if (this.nodeStack.length === 0) return; |
| 6530 | const callerId = this.nodeStack[this.nodeStack.length - 1]; |
| 6531 | if (!callerId) return; |
| 6532 | |
| 6533 | // Get the callee name — first child is typically the identifier or exprDot |
| 6534 | const firstChild = node.namedChild(0); |
| 6535 | if (!firstChild) return; |
| 6536 | |
| 6537 | let calleeName = ''; |
| 6538 | if (firstChild.type === 'exprDot') { |
| 6539 | // Chained static-factory call: `TFoo.GetInstance().DoIt()` — the exprDot's |
| 6540 | // receiver is itself an `exprCall`, so the bare identifier list would |
| 6541 | // collapse to just `DoIt` and mis-resolve to a same-named method on an |
| 6542 | // unrelated class. Encode `TFoo.GetInstance().DoIt` so resolution infers |
| 6543 | // DoIt's class from what `TFoo.GetInstance` RETURNS (#645/#608). Only a |
| 6544 | // capitalized class-factory chain; a unary outer method. |
| 6545 | const innerCall = firstChild.namedChildren.find((c: SyntaxNode) => c.type === 'exprCall'); |
| 6546 | const outerId = firstChild.namedChildren.filter((c: SyntaxNode) => c.type === 'identifier').pop(); |
| 6547 | const method = outerId ? getNodeText(outerId, this.source) : ''; |
| 6548 | if (innerCall && method && /^\w+$/.test(method)) { |
| 6549 | const innerFirst = innerCall.namedChild(0); |
| 6550 | let innerCallee = ''; |
| 6551 | if (innerFirst?.type === 'exprDot') { |
| 6552 | innerCallee = innerFirst.namedChildren |
| 6553 | .filter((c: SyntaxNode) => c.type === 'identifier') |
| 6554 | .map((id: SyntaxNode) => getNodeText(id, this.source)) |
| 6555 | .join('.'); |
| 6556 | } else if (innerFirst?.type === 'identifier') { |
| 6557 | innerCallee = getNodeText(innerFirst, this.source); |
| 6558 | } |
| 6559 | // Gate on the Delphi type-naming convention — `TFoo` classes / `IFoo` |
| 6560 | // interfaces — so a class-factory chain re-encodes but a capitalized |
| 6561 | // VARIABLE/parameter chain (Pascal capitalizes locals too: `Curve.X().Y()`, |
| 6562 | // `Self.X().Y()`) stays bare and keeps its existing bare-name resolution. |
| 6563 | calleeName = innerCallee && /^[TI][A-Z]/.test(innerCallee) |
| 6564 | ? `${innerCallee}().${method}` |
| 6565 | : method; |
| 6566 | } else { |
| 6567 | // Qualified call: Obj.Method(...) |
| 6568 | const identifiers = firstChild.namedChildren.filter( |
| 6569 | (c: SyntaxNode) => c.type === 'identifier' |
| 6570 | ); |
| 6571 | if (identifiers.length > 0) { |
| 6572 | calleeName = identifiers.map((id: SyntaxNode) => getNodeText(id, this.source)).join('.'); |
| 6573 | } |
| 6574 | } |
| 6575 | } else if (firstChild.type === 'identifier') { |
| 6576 | calleeName = getNodeText(firstChild, this.source); |
| 6577 | } |
| 6578 | |
| 6579 | if (calleeName) { |
| 6580 | this.unresolvedReferences.push({ |
| 6581 | fromNodeId: callerId, |
| 6582 | referenceName: calleeName, |
| 6583 | referenceKind: 'calls', |
| 6584 | line: node.startPosition.row + 1, |
| 6585 | column: node.startPosition.column, |
no test coverage detected