* OTP application resource file (` .app.src` / ` .app`): a single * `{application, Name, Props}.` term the grammar parses as a top-level * expression. Two properties carry graph structure — `{mod, {Mod, _Args}}` * names the application-callback module (the app's entry point), and * `{app
(node: SyntaxNode, ctx: ExtractorContext)
| 224 | * out-of-repo apps stay unresolved. |
| 225 | */ |
| 226 | function handleAppResourceTuple(node: SyntaxNode, ctx: ExtractorContext): boolean { |
| 227 | const parentId = ctx.nodeStack[ctx.nodeStack.length - 1]; |
| 228 | const props = node.namedChildren[2]; |
| 229 | if (!parentId || props?.type !== 'list') return true; |
| 230 | const ref = (nameNode: SyntaxNode, kind: 'references' | 'imports'): void => { |
| 231 | const name = atomText(nameNode, ctx.source); |
| 232 | if (!name) return; |
| 233 | ctx.addUnresolvedReference({ |
| 234 | fromNodeId: parentId, |
| 235 | referenceName: name, |
| 236 | referenceKind: kind, |
| 237 | line: nameNode.startPosition.row + 1, |
| 238 | column: nameNode.startPosition.column, |
| 239 | }); |
| 240 | }; |
| 241 | for (const prop of props.namedChildren) { |
| 242 | if (prop.type !== 'tuple' || prop.namedChildren.length < 2) continue; |
| 243 | const key = prop.namedChildren[0]; |
| 244 | const value = prop.namedChildren[1]; |
| 245 | if (!key || key.type !== 'atom' || !value) continue; |
| 246 | const keyName = atomText(key, ctx.source); |
| 247 | if (keyName === 'mod' && value.type === 'tuple') { |
| 248 | const mod = value.namedChildren[0]; |
| 249 | if (mod?.type === 'atom') ref(mod, 'references'); |
| 250 | } else if ( |
| 251 | (keyName === 'applications' || keyName === 'included_applications') && |
| 252 | value.type === 'list' |
| 253 | ) { |
| 254 | for (const app of value.namedChildren) { |
| 255 | if (app.type === 'atom') ref(app, 'imports'); |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | return true; // nothing else in an app term carries graph structure |
| 260 | } |
| 261 | |
| 262 | export const erlangExtractor: LanguageExtractor = { |
| 263 | functionTypes: ['fun_decl'], // dispatched via visitNode (name lives on the clause) |