* Extract function calls from a Pascal expression
(node: SyntaxNode)
| 6473 | * Extract function calls from a Pascal expression |
| 6474 | */ |
| 6475 | private extractPascalCall(node: SyntaxNode): void { |
| 6476 | if (this.nodeStack.length === 0) return; |
| 6477 | const callerId = this.nodeStack[this.nodeStack.length - 1]; |
| 6478 | if (!callerId) return; |
| 6479 | |
| 6480 | // Get the callee name — first child is typically the identifier or exprDot |
| 6481 | const firstChild = node.namedChild(0); |
| 6482 | if (!firstChild) return; |
| 6483 | |
| 6484 | let calleeName = ''; |
| 6485 | if (firstChild.type === 'exprDot') { |
| 6486 | // Chained static-factory call: `TFoo.GetInstance().DoIt()` — the exprDot's |
| 6487 | // receiver is itself an `exprCall`, so the bare identifier list would |
| 6488 | // collapse to just `DoIt` and mis-resolve to a same-named method on an |
| 6489 | // unrelated class. Encode `TFoo.GetInstance().DoIt` so resolution infers |
| 6490 | // DoIt's class from what `TFoo.GetInstance` RETURNS (#645/#608). Only a |
| 6491 | // capitalized class-factory chain; a unary outer method. |
| 6492 | const innerCall = firstChild.namedChildren.find((c: SyntaxNode) => c.type === 'exprCall'); |
| 6493 | const outerId = firstChild.namedChildren.filter((c: SyntaxNode) => c.type === 'identifier').pop(); |
| 6494 | const method = outerId ? getNodeText(outerId, this.source) : ''; |
| 6495 | if (innerCall && method && /^\w+$/.test(method)) { |
| 6496 | const innerFirst = innerCall.namedChild(0); |
| 6497 | let innerCallee = ''; |
| 6498 | if (innerFirst?.type === 'exprDot') { |
| 6499 | innerCallee = innerFirst.namedChildren |
| 6500 | .filter((c: SyntaxNode) => c.type === 'identifier') |
| 6501 | .map((id: SyntaxNode) => getNodeText(id, this.source)) |
| 6502 | .join('.'); |
| 6503 | } else if (innerFirst?.type === 'identifier') { |
| 6504 | innerCallee = getNodeText(innerFirst, this.source); |
| 6505 | } |
| 6506 | // Gate on the Delphi type-naming convention — `TFoo` classes / `IFoo` |
| 6507 | // interfaces — so a class-factory chain re-encodes but a capitalized |
| 6508 | // VARIABLE/parameter chain (Pascal capitalizes locals too: `Curve.X().Y()`, |
| 6509 | // `Self.X().Y()`) stays bare and keeps its existing bare-name resolution. |
| 6510 | calleeName = innerCallee && /^[TI][A-Z]/.test(innerCallee) |
| 6511 | ? `${innerCallee}().${method}` |
| 6512 | : method; |
| 6513 | } else { |
| 6514 | // Qualified call: Obj.Method(...) |
| 6515 | const identifiers = firstChild.namedChildren.filter( |
| 6516 | (c: SyntaxNode) => c.type === 'identifier' |
| 6517 | ); |
| 6518 | if (identifiers.length > 0) { |
| 6519 | calleeName = identifiers.map((id: SyntaxNode) => getNodeText(id, this.source)).join('.'); |
| 6520 | } |
| 6521 | } |
| 6522 | } else if (firstChild.type === 'identifier') { |
| 6523 | calleeName = getNodeText(firstChild, this.source); |
| 6524 | } |
| 6525 | |
| 6526 | if (calleeName) { |
| 6527 | this.unresolvedReferences.push({ |
| 6528 | fromNodeId: callerId, |
| 6529 | referenceName: calleeName, |
| 6530 | referenceKind: 'calls', |
| 6531 | line: node.startPosition.row + 1, |
| 6532 | column: node.startPosition.column, |
no test coverage detected