(node: SyntaxNode, ctx: ExtractorContext)
| 97 | } |
| 98 | |
| 99 | function handleFunDecl(node: SyntaxNode, ctx: ExtractorContext): boolean { |
| 100 | const clauses = node.namedChildren.filter((c) => c.type === 'function_clause'); |
| 101 | const first = clauses[0]; |
| 102 | if (!first) return true; // macro-templated clause (`?M(...) -> ...`) — no static name |
| 103 | const nameNode = getChildByField(first, 'name'); |
| 104 | if (!nameNode) return true; |
| 105 | const name = atomText(nameNode, ctx.source); |
| 106 | if (!name) return true; |
| 107 | |
| 108 | // Continuation clause: extend the existing node's span and attribute this |
| 109 | // clause's calls to it. |
| 110 | if (ctx.filePath === lastFnFile && name === lastFnName && lastFnId) { |
| 111 | for (let i = ctx.nodes.length - 1; i >= 0; i--) { |
| 112 | const n = ctx.nodes[i]; |
| 113 | if (n && n.id === lastFnId) { |
| 114 | if (node.endPosition.row + 1 > n.endLine) n.endLine = node.endPosition.row + 1; |
| 115 | break; |
| 116 | } |
| 117 | } |
| 118 | ctx.pushScope(lastFnId); |
| 119 | for (const clause of clauses) ctx.visitFunctionBody(clause, lastFnId); |
| 120 | ctx.popScope(); |
| 121 | return true; |
| 122 | } |
| 123 | |
| 124 | const spec = precedingSpec(node, name, ctx.source); |
| 125 | const exports = moduleExports(node, ctx.source, ctx.filePath); |
| 126 | const fn = ctx.createNode('function', name, node, { |
| 127 | docstring: getPrecedingDocstring(spec ?? node, ctx.source), |
| 128 | signature: spec |
| 129 | ? collapseWs(getNodeText(spec, ctx.source)).slice(0, 300) |
| 130 | : clauseHeader(first, ctx.source), |
| 131 | isExported: exports === 'all' || exports.has(name), |
| 132 | }); |
| 133 | if (!fn) return true; |
| 134 | ctx.pushScope(fn.id); |
| 135 | // The whole clause is walked (not just the body) so record patterns in the |
| 136 | // arguments and guard calls contribute references too. |
| 137 | for (const clause of clauses) ctx.visitFunctionBody(clause, fn.id); |
| 138 | ctx.popScope(); |
| 139 | lastFnFile = ctx.filePath; |
| 140 | lastFnName = name; |
| 141 | lastFnId = fn.id; |
| 142 | return true; |
| 143 | } |
| 144 | |
| 145 | function handleRecordDecl(node: SyntaxNode, ctx: ExtractorContext): boolean { |
| 146 | const nameNode = getChildByField(node, 'name'); |
no test coverage detected