* Delegate a ` ... ` tag body to the cfscript grammar. * With `parentClassName` set (the block sits at component scope), functions * declared at the script's top level are the component's methods * (` function configure(){}` — the standard ColdBox
(scriptTag: SyntaxNode, parentId: string | undefined, parentClassName?: string)
| 334 | * (closures) keep kind `function`. |
| 335 | */ |
| 336 | private delegateScriptTag(scriptTag: SyntaxNode, parentId: string | undefined, parentClassName?: string): void { |
| 337 | const content = scriptTag.namedChildren.find((c: SyntaxNode) => c.type === 'cf_script_content'); |
| 338 | if (!content) return; |
| 339 | |
| 340 | const inner = this.source.substring(content.startIndex, content.endIndex); |
| 341 | const startLine = content.startPosition.row; |
| 342 | |
| 343 | const extractor = new TreeSitterExtractor(this.filePath, inner, 'cfscript'); |
| 344 | const result = extractor.extract(); |
| 345 | |
| 346 | // The inner TreeSitterExtractor always synthesizes its own `file`-kind |
| 347 | // node scoped to just this snippet — drop it (and any edges touching it) |
| 348 | // since this tag-based file already owns one correctly-ranged file node |
| 349 | // (see createFileNode); the per-node `parentId` contains-edge below |
| 350 | // already links every emitted symbol into the real tree. |
| 351 | const innerFileNodeId = result.nodes.find((n) => n.kind === 'file')?.id; |
| 352 | // Snippet-top-level symbols are the ones the inner extractor attached |
| 353 | // directly to its (dropped) snippet file node — as opposed to closures |
| 354 | // nested inside another function. |
| 355 | const topLevelIds = new Set( |
| 356 | result.edges |
| 357 | .filter((e) => e.kind === 'contains' && e.source === innerFileNodeId) |
| 358 | .map((e) => e.target) |
| 359 | ); |
| 360 | // Snippet-top-level non-callables: `var x = …` locals of the enclosing |
| 361 | // function that the fragment-as-module parse mints as declarations. |
| 362 | const localVarIds = new Set( |
| 363 | result.nodes |
| 364 | .filter((n) => topLevelIds.has(n.id) && (n.kind === 'variable' || n.kind === 'constant')) |
| 365 | .map((n) => n.id) |
| 366 | ); |
| 367 | for (const node of result.nodes) { |
| 368 | if (node.kind === 'file') continue; |
| 369 | node.startLine += startLine; |
| 370 | node.endLine += startLine; |
| 371 | node.language = this.language; |
| 372 | if (parentClassName) { |
| 373 | if (node.kind === 'function' && topLevelIds.has(node.id)) { |
| 374 | node.kind = 'method'; |
| 375 | } |
| 376 | node.qualifiedName = `${parentClassName}::${node.qualifiedName}`; |
| 377 | } |
| 378 | this.nodes.push(node); |
| 379 | if (parentId) { |
| 380 | this.edges.push({ source: parentId, target: node.id, kind: 'contains' }); |
| 381 | } |
| 382 | } |
| 383 | for (const edge of result.edges) { |
| 384 | if (edge.source === innerFileNodeId || edge.target === innerFileNodeId) continue; |
| 385 | if (edge.line) edge.line += startLine; |
| 386 | this.edges.push(edge); |
| 387 | } |
| 388 | for (const ref of result.unresolvedReferences) { |
| 389 | ref.line += startLine; |
| 390 | ref.filePath = this.filePath; |
| 391 | ref.language = this.language; |
| 392 | // Calls inside a <cfscript> body with no enclosing function (rare — a |
| 393 | // top-level script in a .cfm template, or any statement directly in |
no test coverage detected