feat(extraction): index Erlang escripts and OTP app resource files (#635, #648) (#1169)

escripts (.escript) index like any module — the ELP grammar has a
first-class shebang node, so no source transform is needed; main/1 and its
helpers get full function/call extraction.

OTP application resource files (<app>.app.src and compiled <app>.app) join
the graph as Erlang terms the grammar parses natively. They route by full
suffix (their last-dot extension, .src, is far too generic for the
extension map). The application tuple yields structure: {mod, {Mod, _}}
links the app to its callback module — the app's entry point — and
{applications, [...]} / {included_applications, [...]} connect umbrella
sibling apps, resolving through the OTP app-name == module-name convention;
kernel/stdlib and other out-of-repo apps stay unresolved.

App-file refs resolve only ever to MODULES: validation on emqx caught the
ssl OTP-app dependency resolving to a test helper FUNCTION named ssl (the
same defect class as the earlier -behaviour gate), so the matchReference
module-only gate now covers every ref an .app/.app.src file emits.

Validated on emqx: 2 app.src + 6 escripts indexed, entry-module and
umbrella-dependency edges all namespace-targeted post-gate, escript
functions extracted; a stray legacy/module.src stays unknown.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-03 15:32:59 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent a5b8cd8e25
commit a0208feaac
7 changed files with 173 additions and 4 deletions
+29
View File
@@ -142,6 +142,35 @@ describe('Resolution Module', () => {
// In-repo behaviour module resolves to its namespace.
const resolved = matchReference(mkRef('my_behaviour'), context);
expect(resolved?.targetNodeId).toBe(behaviourModule.id);
// The same module-only rule covers refs emitted by .app/.app.src
// resource files: on emqx, the `ssl` OTP app dependency resolved to a
// test helper FUNCTION named ssl. A colliding non-module name stays
// unresolved; a real umbrella-sibling module resolves.
nodes.push({
id: 'function:test/ldap_SUITE.erl:ssl:12',
kind: 'function',
name: 'ssl',
qualifiedName: 'ldap_SUITE::ssl',
filePath: 'test/ldap_SUITE.erl',
language: 'erlang',
startLine: 12,
endLine: 14,
startColumn: 0,
endColumn: 0,
updatedAt: Date.now(),
});
const appRef = (name: string) => ({
fromNodeId: 'file:src/myapp.app.src',
referenceName: name,
referenceKind: 'imports' as const,
line: 6,
column: 0,
filePath: 'src/myapp.app.src',
language: 'erlang' as const,
});
expect(matchReference(appRef('ssl'), context)).toBeNull();
expect(matchReference(appRef('my_behaviour'), context)?.targetNodeId).toBe(behaviourModule.id);
});
it('should prefer same-module candidates over cross-module matches', () => {