* ` ... `. * The grammar's implicit-end-tag scanner means component body content * (cffunction tags, cfscript tags, etc.) appears as the open tag's FOLLOWING * siblings in `program`, not nested children — walk forward to the mat
(openTag: SyntaxNode, containerId: string | undefined)
| 173 | * cf_component_close_tag. |
| 174 | */ |
| 175 | private extractComponent(openTag: SyntaxNode, containerId: string | undefined): SyntaxNode { |
| 176 | const name = this.tagAttr(openTag, 'name') ?? this.componentNameFromPath(); |
| 177 | const id = generateNodeId(this.filePath, 'class', name, openTag.startPosition.row + 1); |
| 178 | |
| 179 | const classNode: Node = { |
| 180 | id, |
| 181 | kind: 'class', |
| 182 | name, |
| 183 | qualifiedName: `${this.filePath}::${name}`, |
| 184 | filePath: this.filePath, |
| 185 | language: this.language, |
| 186 | startLine: openTag.startPosition.row + 1, |
| 187 | endLine: openTag.startPosition.row + 1, // extended below once the close tag is found |
| 188 | startColumn: openTag.startPosition.column, |
| 189 | endColumn: openTag.endPosition.column, |
| 190 | isExported: true, |
| 191 | updatedAt: Date.now(), |
| 192 | }; |
| 193 | this.nodes.push(classNode); |
| 194 | if (containerId) { |
| 195 | this.edges.push({ source: containerId, target: classNode.id, kind: 'contains' }); |
| 196 | } |
| 197 | |
| 198 | const extendsName = this.tagAttr(openTag, 'extends'); |
| 199 | if (extendsName) { |
| 200 | this.unresolvedReferences.push({ |
| 201 | fromNodeId: classNode.id, |
| 202 | referenceName: extendsName, |
| 203 | referenceKind: 'extends', |
| 204 | filePath: this.filePath, |
| 205 | line: openTag.startPosition.row + 1, |
| 206 | column: openTag.startPosition.column, |
| 207 | language: this.language, |
| 208 | }); |
| 209 | } |
| 210 | const implementsAttr = this.tagAttr(openTag, 'implements'); |
| 211 | if (implementsAttr) { |
| 212 | for (const iface of implementsAttr.split(',').map((s) => s.trim()).filter(Boolean)) { |
| 213 | this.unresolvedReferences.push({ |
| 214 | fromNodeId: classNode.id, |
| 215 | referenceName: iface, |
| 216 | referenceKind: 'implements', |
| 217 | filePath: this.filePath, |
| 218 | line: openTag.startPosition.row + 1, |
| 219 | column: openTag.startPosition.column, |
| 220 | language: this.language, |
| 221 | }); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // Walk siblings between the open tag and its close tag. |
| 226 | let sibling = openTag.nextSibling; |
| 227 | let lastNode: SyntaxNode = openTag; |
| 228 | while (sibling) { |
| 229 | if (sibling.type === 'cf_component_close_tag') { |
| 230 | lastNode = sibling; |
| 231 | break; |
| 232 | } |
no test coverage detected