(node: SyntaxNode, ctx: ExtractorContext)
| 173 | } |
| 174 | |
| 175 | function handlePpDefine(node: SyntaxNode, ctx: ExtractorContext): boolean { |
| 176 | const lhs = getChildByField(node, 'lhs'); |
| 177 | const nameNode = lhs ? getChildByField(lhs, 'name') : null; |
| 178 | if (!nameNode) return true; |
| 179 | const macro = ctx.createNode('constant', getNodeText(nameNode, ctx.source), node, { |
| 180 | signature: collapseWs(getNodeText(node, ctx.source)).slice(0, 200), |
| 181 | }); |
| 182 | // The replacement's calls execute at expansion sites, but attributing them |
| 183 | // to the MACRO node keeps them true exactly once: `-define(LOG_AUDIT(E), |
| 184 | // audit_logger:log(E))` gives the LOG_AUDIT constant a `calls` edge to the |
| 185 | // logger, and each `?LOG_AUDIT(...)` use site links to the constant (see the |
| 186 | // macro_call_expr case in extractCall) — so the chain |
| 187 | // `caller → LOG_AUDIT → audit_logger:log` traverses without minting a |
| 188 | // per-use duplicate of the body's calls. |
| 189 | const replacement = getChildByField(node, 'replacement'); |
| 190 | if (macro && replacement) { |
| 191 | ctx.pushScope(macro.id); |
| 192 | ctx.visitFunctionBody(replacement, macro.id); |
| 193 | ctx.popScope(); |
| 194 | } |
| 195 | return true; |
| 196 | } |
| 197 | |
| 198 | function handleBehaviour(node: SyntaxNode, ctx: ExtractorContext): boolean { |
| 199 | const nameNode = getChildByField(node, 'name'); |
no test coverage detected