Context
CodeGraph has no Perl support today — .pl / .pm files aren't detected, so a Perl codebase indexes to zero symbols. Perl is item #9 on the umbrella language-coverage request in #1533; this is a focused issue for just that item, with a PR attached.
Perl is still the backbone of a lot of long-lived infrastructure: test harnesses, build and release tooling, DevOps automation, and large in-house systems that have accumulated for decades. Those are exactly the codebases where nobody remembers how anything connects, and where a graph is worth the most.
Why Perl is worth doing
Adding it turned out to be cheap. The tree-sitter-perl project publishes an official prebuilt tree-sitter-perl.wasm on its v2.0.0 release, and it's ABI 15 — so it drops straight into the existing vendored-grammar path with no emscripten or Docker build, and none of the ABI-13 shared-heap corruption that has bitten other grammars.
What makes Perl interesting to model
Two things don't fall out of generic tree-sitter handling, and getting them wrong quietly produces a useless graph:
1. Package scoping. The dominant idiom scopes by following sibling, not by containment:
package Sample::Widget;
sub new { ... }
sub name { ... }
new and name are siblings of the package node, not children. Generic parent/child containment attaches them to the file and loses the Sample::Widget::new qualified name entirely.
And a package name is always absolute — this declares Inner, never Outer::Inner:
package Outer;
package Inner { sub b { ... } }
2. Method calls. $obj->method() parses as method_call_expression with invocant / method fields — none of the function / object / name fields the generic call path looks for. Left alone, it takes the invocant as the callee, so $obj->configure() records a call to $obj and configure's callers are invisible.
Where Perl can't be answered statically
Perl resolves a lot at run time, and this is where a naive implementation actively hurts. Each of these was measured emitting wrong edges on a real 913-file Perl codebase:
- Chained invocant —
$self->service_db()->attribute_add(%opts) dispatches on whatever service_db() returned. Emitting the bare name attribute_add let it exact-match the enclosing same-named sub, producing hundreds of wrong self-referential call edges.
- Dynamic method —
$obj->$method() emitted $method, which name-matching bound to the variable of that name — an edge to something that isn't callable.
SUPER:: — dispatches to a parent's method, but with no inheritance/MRO model the bare name binds to the child's own override, i.e. precisely the method Perl will not run.
- Builtins —
push, die, print, join parse as ordinary calls and can never resolve to a project symbol.
Consistent with the project's "silent beats wrong" principle, none of these should emit an edge.
Ambiguous extensions
.t is Perl's standard test suffix but is also Raku/Terra/Turing; .cgi is as often Python or shell. A shebang naming another interpreter should win over the extension — the same content-based disambiguation .h → C/C++/Objective-C already does.
Proposal
Wire Perl through the existing vendored-wasm path: .pl, .pm, .t, .pod, .psgi, .cgi, with packages as namespaces, subroutines with qualified names, use/require imports (pragmas filtered), use parent/use base inheritance including qw() lists, use constant, package-level variables, and call/instantiation edges — with the run-time-only cases above deliberately left silent.
Projects using non-standard Perl extensions can opt in through codegraph.json's extensions map, so nothing unusual needs to be hardcoded.
I have this working and will open a PR against this issue.
Related: #1533 (item 9).
Context
CodeGraph has no Perl support today —
.pl/.pmfiles aren't detected, so a Perl codebase indexes to zero symbols. Perl is item #9 on the umbrella language-coverage request in #1533; this is a focused issue for just that item, with a PR attached.Perl is still the backbone of a lot of long-lived infrastructure: test harnesses, build and release tooling, DevOps automation, and large in-house systems that have accumulated for decades. Those are exactly the codebases where nobody remembers how anything connects, and where a graph is worth the most.
Why Perl is worth doing
Adding it turned out to be cheap. The
tree-sitter-perlproject publishes an official prebuilttree-sitter-perl.wasmon its v2.0.0 release, and it's ABI 15 — so it drops straight into the existing vendored-grammar path with no emscripten or Docker build, and none of the ABI-13 shared-heap corruption that has bitten other grammars.What makes Perl interesting to model
Two things don't fall out of generic tree-sitter handling, and getting them wrong quietly produces a useless graph:
1. Package scoping. The dominant idiom scopes by following sibling, not by containment:
newandnameare siblings of the package node, not children. Generic parent/child containment attaches them to the file and loses theSample::Widget::newqualified name entirely.And a package name is always absolute — this declares
Inner, neverOuter::Inner:2. Method calls.
$obj->method()parses asmethod_call_expressionwithinvocant/methodfields — none of thefunction/object/namefields the generic call path looks for. Left alone, it takes the invocant as the callee, so$obj->configure()records a call to$objandconfigure's callers are invisible.Where Perl can't be answered statically
Perl resolves a lot at run time, and this is where a naive implementation actively hurts. Each of these was measured emitting wrong edges on a real 913-file Perl codebase:
$self->service_db()->attribute_add(%opts)dispatches on whateverservice_db()returned. Emitting the bare nameattribute_addlet it exact-match the enclosing same-named sub, producing hundreds of wrong self-referential call edges.$obj->$method()emitted$method, which name-matching bound to the variable of that name — an edge to something that isn't callable.SUPER::— dispatches to a parent's method, but with no inheritance/MRO model the bare name binds to the child's own override, i.e. precisely the method Perl will not run.push,die,print,joinparse as ordinary calls and can never resolve to a project symbol.Consistent with the project's "silent beats wrong" principle, none of these should emit an edge.
Ambiguous extensions
.tis Perl's standard test suffix but is also Raku/Terra/Turing;.cgiis as often Python or shell. A shebang naming another interpreter should win over the extension — the same content-based disambiguation.h→ C/C++/Objective-C already does.Proposal
Wire Perl through the existing vendored-wasm path:
.pl,.pm,.t,.pod,.psgi,.cgi, with packages as namespaces, subroutines with qualified names,use/requireimports (pragmas filtered),use parent/use baseinheritance includingqw()lists,use constant, package-level variables, and call/instantiation edges — with the run-time-only cases above deliberately left silent.Projects using non-standard Perl extensions can opt in through
codegraph.json'sextensionsmap, so nothing unusual needs to be hardcoded.I have this working and will open a PR against this issue.
Related: #1533 (item 9).