Language profiles
The profiles @codegraph/core ships: per entity kind, the traits an extractor must emit and the traits it may emit, plus the edge kinds the language can produce, the words the vocabulary reads of it, and the profile’s documented blind spots.
Generated content. Every table on this page is a rendering of codegraph profiles --json; run it for the machine-readable form.
codegraph profiles --json # all eleven profiles
codegraph profiles --lang java # one profile, as textA profile licenses an entity by the rule required(kind) ⊆ traits ⊆ required(kind) ∪ optional(kind). What a profile is — and why it is data rather than code — is on Profiles.
Summary
lang | Language | Kinds | Edge kinds | Notes |
|---|---|---|---|---|
clj | Clojure | 10 | 5 | 13 |
csharp | C# | 15 | 8 | 21 |
ex | Elixir | 9 | 6 | 21 |
go | Go | 10 | 6 | 9 |
java | Java | 12 | 8 | 17 |
js | JavaScript | 8 | 5 | 13 |
php | PHP | 12 | 8 | 10 |
python | Python | 9 | 6 | 10 |
rb | Ruby | 7 | 7 | 27 |
rust | Rust | 13 | 5 | 12 |
ts | TypeScript | 14 | 8 | 28 |
A profile that is specified but has no shipped extractor is complete, not deficient: the profile is the contract an extractor is written against. Today java, csharp, ts, ex and rb have one: see the Java, C#, TypeScript, Elixir and Ruby extractor pages. Every shipped extractor’s model can then be measured by the metrics pass.
Clojure — clj
Edge kinds: import · interfaceImplementation · invocation · access · reference
| Kind | Required traits | Optional traits |
|---|---|---|
defmethod | TInvocable, TAttachedTo, TChildOf, TSourceAnchor | TComment, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
function | TNamed, TStructural, TInvocable | TSourceAnchor, TComment, TChildOf, TWithChildren, TAttachedTo, TTypedEntity, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
implBlock | TWithChildren, TAttachedTo, TSourceAnchor | TChildOf, TComment |
localBinding | TNamed, TStructural, TChildOf | TTypedEntity, TSourceAnchor |
multimethod | TNamed, TStructural, TInvocable, TChildOf | TSourceAnchor, TComment, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
namespace | TNamed, TModule, TWithChildren | TSourceAnchor, TComment |
parameter | TNamed, TStructural, TChildOf | TTypedEntity, TSourceAnchor |
protocol | TNamed, TType, TChildOf | TWithChildren, TSourceAnchor, TComment |
record | TNamed, TType, TChildOf | TWithImplements, TWithChildren, TSourceAnchor, TComment |
var | TNamed, TStructural | TSourceAnchor, TComment, TChildOf, TWithChildren, TAttachedTo, TTypedEntity, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
Lexicon — what the vocabulary reads of this language
- Reserved words:
def·if·do·let·quote·var·fn·loop·recur·throw·try·catch·finally·monitor-enter·monitor-exit·new·set!·nil·true·false - Conventional names:
-main·-init·-post-init·-toString·-equals·-hashCode - Technical words:
coll·kw·ns·seq·sym·xf·xform - Qualified kinds:
namespaceby.
Notes
- Planned extractor: clj-kondo analysis output (
--config '{:output {:analysis true}}'), which resolves var definitions, usages and namespace requires without loading the code. - Multimethod dispatch is not statically resolvable: the dispatch fn can compute any value. Invocation edges into a multimethod carry provenance
dynamic-candidatewith every knowndefmethodof that multimethod incandidates, andtoset to the best guess. - Macro-generated code does not exist before expansion. Definitions a macro emits are invisible to a reader-level extractor; where an extractor does expand (or where the macro is known, e.g.
defrecordexpanding to a class), the resulting entities and edges carry provenancegenerated. extend-type/extend-protocolare reified as anonymousimplBlockentities (METAMODEL.md §7). The block isattachedTothe extended type; theinterfaceImplementationedge runs type -> protocol and uses the block as its anchor.- Protocol methods are namespace-level vars: they are emitted as
functionchildren of the namespace andattachedTothe protocol — the canonical containment-vs-attachment split, same shape asdefmethod. - Protocols are open: any namespace may extend any type to any protocol at any time, so the implementer set of a protocol is never closed by the file that declares it, and a partial corpus systematically under-reports
interfaceImplementation. - No
inheritanceedge kind: Clojure has no class inheritance.derivebuilds ad-hoc keyword hierarchies used for dispatch, which are values, not entities, and are not modeled. declaredTypeis populated only from^TypeHintmetadata, which is optional and rare; for the vast majority of vars, parameters and bindings it is absent — this is precisely whyTTypedEntity.declaredTypeis optional.defrecord/deftypepositional fields are emitted asparameterchildren of the record: in Clojure they are literally the positional constructor parameters.- Destructuring forms (
{:keys [a b]},[x & rest]) are flattened to oneparameterper bound symbol, in source order; the pattern itself is not reified. - Higher-order use of vars (
(map inc coll),(partial f x)) yieldsreferenceedges to the var, neverinvocationedges — the call happens elsewhere and its site is unknown. - Runtime resolution (
resolve,requiring-resolve,eval,(var-get (ns-resolve ...))) and dynamically computedrequireare static blind spots: noimportorinvocationedge is emitted for them. :refer :alland:referbring vars into scope without qualification; theimportedge is namespace -> namespace, so the module layer stays exact even when a symbol’s origin needs var resolution.
C# — csharp
Edge kinds: import · inheritance · interfaceImplementation · invocation · access · reference · annotationUse · throws
| Kind | Required traits | Optional traits |
|---|---|---|
class | TNamed, TType, TWithInheritances, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
constructor | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TComment, TMetrics |
delegate | TNamed, TType, TInvocable, TWithChildren, TWithParameters, TTypedEntity, TChildOf, TSourceAnchor | TComment, TMetrics |
enum | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TTypedEntity, TComment, TMetrics |
event | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
field | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment, TWithValue |
interface | TNamed, TType, TWithInheritances, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
lambda | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TTypedEntity, TMetrics |
localVariable | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
method | TNamed, TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TAttachedTo, TComment, TMetrics |
namespace | TNamed, TModule, TWithChildren | TChildOf, TComment |
parameter | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor, TWithValue |
property | TNamed, TStructural, TTypedEntity, TWithChildren, TChildOf, TSourceAnchor | TWithInvocations, TWithAccesses, TAttachedTo, TComment, TMetrics |
record | TNamed, TType, TWithInheritances, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
struct | TNamed, TType, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
Lexicon — what the vocabulary reads of this language
- Reserved words:
abstract·as·base·bool·break·byte·case·catch·char·checked·class·const·continue·decimal·default·delegate·do·double·else·enum·event·explicit·extern·false·finally·fixed·float·for·foreach·goto·if·implicit·in·int·interface·internal·is·lock·long·namespace·new·null·object·operator·out·override·params·private·protected·public·readonly·ref·return·sbyte·sealed·short·sizeof·stackalloc·static·string·struct·switch·this·throw·true·try·typeof·uint·ulong·unchecked·unsafe·ushort·using·virtual·void·volatile·while - Conventional names:
ToString·Equals·GetHashCode·Finalize·Main·CompareTo·Dispose·DisposeAsync·GetEnumerator·GetAsyncEnumerator·MoveNext·MoveNextAsync·Clone·Deconstruct·this[]·op_Addition·op_Subtraction·op_Multiply·op_Division·op_Modulus·op_Equality·op_Inequality·op_LessThan·op_GreaterThan·op_LessThanOrEqual·op_GreaterThanOrEqual·op_Implicit·op_Explicit·op_UnaryNegation·op_UnaryPlus·op_LogicalNot·op_OnesComplement·op_Increment·op_Decrement·op_True·op_False·op_BitwiseAnd·op_BitwiseOr·op_ExclusiveOr·op_LeftShift·op_RightShift·op_UnsignedRightShift - Technical words:
attribute·enumerable·enumerator·linq - Qualified kinds:
namespaceby. - Interface prefix: a leading
Ibefore Upper + lower is a marker, not a word
Notes
- Partial classes (and partial methods) mean one entity id is declared across several files: the entity carries a single anchor for its primary declaration, while each edge carries sourceFile to say which declaration site produced it. Merging by id is correct here, not a collision.
var, target-typednew(), anonymous types and inferred lambda parameters leave TTypedEntity present with declaredType absent — the trait states that the entity has a type, not that the extractor resolved one.- Extension methods are children of their static host class but carry TAttachedTo pointing at the extended type; a call site written as instance syntax is still an invocation edge to the static method.
- Reflection is invisible: Type.GetType, Activator.CreateInstance, expression trees and source-generated partials leave no edge unless the generated source is part of the analyzed root (then provenance is “generated”).
- Dependency-injection wiring is invisible: container registrations (Microsoft.Extensions.DependencyInjection, Autofac, …) bind an interface to an implementation at runtime, so no invocation edge links a consumer to the concrete type it will receive.
- Virtual dispatch and calls through an interface resolve to the DECLARED member (the one the source names); the implementation actually run is a whole-corpus question the analyzer answers. A
dynamicreceiver binds to no symbol at all, so the call site is dropped and counted in the stderr summary — an extractor guessing targets from the file’susingdirectives would be inventing edges. usingdirectives,global usingand using aliases are folded to module-level import edges to the imported namespace, never to individual types.- Corpus membership is a whitelist of the type symbols DECLARED in the analyzed sources, never a namespace-prefix test. Roslyn resolves what it can against the BCL reference assemblies the extractor carries (so
stringis a stub in moduleSystem, with its real namespace); a name it cannot bind is an error type, emitted as a stub in the reserved module<unresolved>under the name as written — the honest form, since guessing a namespace from the file’susingdirectives would invent a fully-qualified name. - Generic arity is part of a type’s symbol (
Repository`1, Roslyn’s metadata name):Foo,Foo<T>andFoo<T,U>legally coexist in one namespace, so Java-style erasure to the bare name would merge three declarations into one entity. Signatures use fully-qualified metadata names WITH type arguments (System.Func`2<!!0,System.String>), because C# can overload on type arguments alone (Humanize(Func<T,string>)besideHumanize(Func<T,object>), found on Humanizer); type parameters are ECMA-335 ordinals, nullable annotations and ref/out/in are dropped. - A corpus is not a compilation unit: two projects that never see each other may both declare a non-partial
static class Extensionswith the same member, and every service has its own top-levelProgram.cs(found on dotnet/eShop). In one compilation they are one type with several same-keyed members. Every one is kept: the first declaration in ordinal file order owns the plain key, each later one is re-keyed by its file (…#in:Catalog.API/Program.cs, a source fact like a lambda’s position), and every re-keying is named on stderr. Dropping later ones — the JDT rule the Java profile documents — would erase nine services’ DI wiring out of ten. - Members of a C# 14 extension block (
extension(Money m) { … }, found on Humanizer) are members of the enclosing static class carrying TAttachedTo → the receiver type, exactly like classic extension methods; the block itself, a nameless nested type to Roslyn, is no entity. - Microsoft.NET.Sdk’s seven implicit global usings (System, System.Collections.Generic, System.IO, System.Linq, System.Net.Http, System.Threading, System.Threading.Tasks — what
<ImplicitUsings>enablegenerates into obj/, which is build output and skipped) are added to the compilation by default (--implicit-usings sdk); without themTask,List<T>andCancellationTokenwere the most-referenced unresolved names on OrchardCore. The Web SDK’s additions (Microsoft.AspNetCore.*,Microsoft.Extensions.*) are opt-in (web): one compilation cannot apply them per project, and on a mixed corpus they make names ambiguous — OrchardCore’s ownStartupBasecollided 345 times withMicrosoft.AspNetCore.Hosting.StartupBase. Implicit usings write no import edge: no file wrote them. The ASP.NET Core shared framework’s reference pack is embedded beside the BCL’s when the building SDK has it. - An ambiguous name is unresolved: two corpus types with one simple name whose projects each
global usingtheir own namespace (dotnet/eShop’s twoCatalogItem) collide once those usings merge into one compilation, and the reference lands in<unresolved>rather than on a guess between the two. - Two parameters may share a name (
(_, _) => …discards, found on OrchardCore): the second and later same-named parameters carry their ordinal (#param:_:1). - A nameless invocable (lambda, anonymous method) is identified by
#file:line:column; a column is a source fact, and two can start on one line. A local’s id carries line AND column for the same reason (#local:name:line:column); a local function is amethodbelow its invocable (#fn:Name(params)). - Members Roslyn synthesizes and nobody wrote — a record’s Equals/GetHashCode/Deconstruct/copy constructor, backing fields, an enum’s
value__— are not entities, and neither are accessors (their bodies charge to the property). A call to such a member folds to its containing type, the rule that already governs members of stubs. The one implicit member emitted is the parameterless constructor, anchored at the type’s header line sonew T()does not dangle. - An attribute is an
annotationUseedge carrying its arguments as written values — positional ones named after the bound constructor’s parameters — and nothing inside the attribute produces an access or reference edge of its own. A method group used as a value (new Handler(Zero)) is areferenceto the method, not an invocation. - A
referenceedge comes from the narrowest declared owner of the written type — a parameter’s type from the parameter, a field’s from the field — and locals never own edges: an initializer’s call is the invocable’s dependency, and the local’s own type rides on declaredType. - Primitives resolve:
intISSystem.Int32, so unlike Java they become BCL stubs rather than being omitted.void,dynamic, anonymous types, pointer types and type parameters are not entities and leave declaredType absent. - Neither a
.slnnor a.csprojis read: the extractor parses every*.csunder the source roots into ONE compilation (no MSBuild, no restore), so a legacy tree with no restorable project graph still extracts, and a missing package is a stub rather than a build failure. Consequences: one preprocessor configuration is parsed (no--defineyet); source-generated and XAML/Razor code-behind partials are absent unless their output is under the roots; a name declared twice across projects that never see each other collides as one entity. - The metrics pass (
codegraph-metrics, PLAN.md §18): a property with accessors is joined to its accessor spaces —{ get; set; }is two, so it carries the pass’s charged keys only (cognitive, exits, ABC). An implicit constructor (anchored on the type’s name) and a record’s positional members have no space and carry none of its keys. tree-sitter-c-sharp 0.23.5 misparses a C# 14 extension block: its members go unmeasured by the pass, and the enclosing class is joined by containment (charged keys only). tree-sitter does not evaluate#if: in a file with conditional blocks big-code-analysis measures EVERY branch, where Roslyn — and so this extractor’ssloc— keeps the active one; on Humanizer such files are the 37 exact joins whoseslocand big-code-analysis’splocdisagree.
Elixir — ex
Edge kinds: import · interfaceImplementation · invocation · access · reference · throws
| Kind | Required traits | Optional traits |
|---|---|---|
attribute | TNamed, TStructural, TChildOf, TSourceAnchor | TWithValue, TWithAccesses, TWithInvocations, TComment |
callback | TNamed, TInvocable, TWithChildren, TWithParameters, TChildOf, TSourceAnchor | TComment |
field | TNamed, TStructural, TChildOf, TSourceAnchor | TWithValue, TComment |
file | TNamed, TModule, TWithChildren | TSourceAnchor, TComment, TMetrics, TWithInvocations, TWithAccesses |
function | TNamed, TInvocable, TWithChildren, TWithParameters, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TWithLocalVariables, TComment, TMetrics |
macro | TNamed, TInvocable, TWithChildren, TWithParameters, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TWithLocalVariables, TComment, TMetrics |
module | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TWithImplements, TAttachedTo, TComment, TMetrics, TWithInvocations, TWithAccesses |
parameter | TStructural, TChildOf | TNamed, TSourceAnchor, TWithValue |
protocol | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
Lexicon — what the vocabulary reads of this language
- Reserved words:
true·false·nil·when·and·or·not·in·fn·do·end·catch·rescue·after·else - Conventional names:
__using__·__before_compile__·__after_compile__·__after_verify__·__on_definition__·__struct__·__info__·__deriving__·__impl__·__protocol__·start_link·child_spec·init·handle_call·handle_cast·handle_info·handle_continue·terminate·code_change·format_status·to_string·inspect·@moduledoc·@doc·@typedoc·@spec·@type·@typep·@opaque·@callback·@macrocallback·@optional_callbacks·@impl·@behaviour·@derive·@enforce_keys·@external_resource·@compile·@deprecated·@dialyzer·@on_load·@vsn·@before_compile·@after_compile·@after_verify·@on_definition·@nifs·@fallback_to_any·@file - Technical words:
attrs·behaviour·changeset·conn·ecto·ets·gen·live·mix·otp·phoenix·pid·plug·schema·server·socket·sup·supervisor·web - File kinds:
file - Qualified kinds:
fileby/·moduleby.·protocolby.
Notes
- The module is the file (the TypeScript rule):
TModule.definedInhas exactly one entry, and every entity’s module is the file it is written in. Adefmoduleis amoduleentity, child of its file, carrying TType — the module IS the type in Elixir (a struct is%Mod{}, a behaviour and a protocol are modules,@spec f :: Mod.t()). Nesting (defmodule Innerinsidedefmodule Outer) definesOuter.Inneras a second child of the file and aliasesInnerinsideOuter: it is an alias, not a containment. - Key components are percent-encoded:
/,#and%in every path segment and name, and.inside a module atom (Acme%2EOrder), because.is the nesting separator inside a symbol (Acme%2EOrder.total). Relying on the rule that alias segments start uppercase while function names do not would make raw dots injective fordefmodule A.Band fail fordefmodule :"a.b", which is legal; the encoding is injective by construction, and the entity’snameis the atom as written. - Arity is identity:
f/1andf/2are unrelated functions, so afunction,macroorcallbackalways carries its arity as the disambiguator (total#1).def f(a, b \\ 1)declaresf/1andf/2and is ONE entity,f#2, to which a callf(x)resolves; every clause of onef/2is folded into that entity, anchored from the first clause’s line to the last clause’send.signatureisname/arity. - No
inheritanceedge kind and noembedding: Elixir has neither class inheritance nor embedding.interfaceImplementationis emitteddeclaredfor@behaviour Band fordefimpl P, for: T(an edge T -> P anchored at the block), andgeneratedfor@derive P, whose expansion the language itself defines (Protocol.derive/3). defimpl P, for: Tis a named module on the BEAM (P.T, the compiler’s own name): it is amoduleentity keyed by that name in the file where the block is written,attachedToT — the containment-versus-attachment split of the ClojureimplBlock, with the name Elixir gives it.defimplwritten insidedefmodule Twith nofor:implements for T.- Corpus membership is a whitelist of the
defmodules under the roots, never a name-prefix test. Every other module is a stubmoduleentity below one of two reserved stub modules:<otp>for a module the BEAM ships (Elixir’s standard library and Erlang/OTP, decided by a module and export table embedded in the extractor at build time from the building toolchain —Enumand:etsside by side, one VM; the built-in protocol targetsAny,BitString,Function,PID,Port,Reference,Tupleincluded), and<deps>for a module the roots do not declare and the table does not know (a dependency, whichever one: with nodeps/read, the model cannot say which). Keys therefore never depend on what is checked out beside the corpus. A stub has no members (the C# fold): a call toEnum.map/2is aninvocationof the stub module<otp>/Enum, and a field of an external struct is never an entity. Analiasof a namespace PREFIX no module declares (alias Plausible.Stats.SQLthenSQL.Expression) is dropped and counted asprefix_alias, never stubbed. alias,import,requireanduseare oneimportedge kind, file -> the target: another corpus file when the target is declared under the roots, else the stubmodulebelow<otp>or<deps>. Folded to the module layer by the analyzer, an external import reads as a dependency on the reserved module — the honest statement that the model does not know which dependency provides it. The four forms are counted separately on stderr; the edge itself does not say which was written. A form written inside a function body applies to that body alone and is still an edge from the file.use Xis defined by the language asrequire Xfollowed byX.__using__(opts)at compile time: it yields theimportedge AND adeclaredinvocation ofX.__using__#1. What__using__injects — a@behaviour, imports, definitions — does not exist before expansion: it is absent from the baseline and every local call it would have bound is dropped and counted aslocal-unboundon stderr (a Phoenix router’s routes, an Ecto schema’s fields, aplugpipeline). The--traceenrichment recovers those asgenerated.- A local call
f(a, b)resolves in this order: adef/defp/defmacroof the enclosing module with that name and arity (defaults folded); an explicitimport Mthat exports it (a corpus M, the OTP table, or the--depsexports), most recent import first; Kernel / Kernel.SpecialForms unlessimport Kernel, except:removed it — and a Kernel binding is the LANGUAGE, resolved but never an edge (length/1,+/2, theto_string/1of a string interpolation), counted askernelon stderr; the SOLE foreignimport Mwhose exports are unknown (a stub below<deps>, counted) — unless the moduleuses a foreign module whose__using__may have injected the name, when nothing honest can be said and the site is dropped aslocal_injected; two or more candidates:ambiguous_import; none:local_unbound. A remote callM.f/non a corpus M that declares nof/n(an injected definition, a typo) is dropped asremote_unbound. - Dynamic dispatch is reported, never guessed. A call to a corpus protocol function
P.f(x)is aninvocationwith provenancedynamic-candidatewhose candidates are every corpusdefimplof P’sf;GenServer.call(Server, msg)/GenServer.cast(Server, msg)withServera statically named corpus module (__MODULE__or an alias) is, beside the invocation of<otp>/GenServer, adynamic-candidateinvocation of that module’s clause-foldedhandle_call/3/handle_cast/2— the one message-passing form that is statically honest, because the server is named in the source; a server the module does not define a handler for is dropped ashandler_unbound. Any other first argument (a pid, a registered name, a variable),apply/3,mod.f()through a variable,Module.concat,Code.eval_*andsend/2are dropped and counted asdynamic-dispatch. - Higher-order use (
&M.f/2,&f/2,&M.f(&1, x)) yields areferenceedge to the function, never aninvocation: the call happens elsewhere. A module atom in argument position (children = [MyWorker, {Registry, keys: :unique}]) is areferenceto the module — how a supervision tree surfaces.%M{…}in an expression or a pattern is areferenceto M (struct expansion) and each named key anaccesstoM.<key>(isReadin a pattern,isWritein a construction or update) — the only static field access Elixir has;s.nameis a runtime map access, dropped and counted.@attrread in a body is anaccessto theattribute. raise M,raise M, msgandreraise M, …arethrowsedges to M (RuntimeErrorbelow<otp>forraise "text");throwandexitare not exceptions and yield nothing.@spec,@type,@typepand@opaqueyieldreferenceedges to every named remote type’s module;@typeis not an entity.@moduledocand@docare the entity’scomments, as is the#comment block written directly above a definition.- A
defdelegate f(a), to: M, as: :gis afunctionwith onedeclaredinvocation ofM.g#1: the delegation is written.defpanddefare one kind — the metamodel has no visibility — andprivate: truerides as a pass-through key the container contract tolerates;defaults: nsays how many lower arities a default-carrying head also declares. - A module body is compile-time code:
use X,plug :x,schema "t" do field … end, anif Mix.env() == :test do def … end— its calls and reads are edges FROM themoduleentity (hence the optionalTWithInvocations/TWithAccesseson the kind), the calls injected macros would bind being counted rather than guessed. A module attribute’s value owns the edges it writes (the narrowest declared owner):@table :ets.new(…)invokes from theattribute,@names [Acme.A, Acme.B]references from it,@limit @max * 2reads from it. A reserved attribute (@moduledoc,@doc,@spec,@impl,@enforce_keys, …) is metadata, never an entity; a redefined attribute keeps its first definition (accumulating attributes are written many times) and the rest are counted. - Locals are deferred:
TWithLocalVariablesis licensed on invocables and not emitted in M15. Every match binds, so a local per=would multiply entities by ten for no edge the analyzer reads today; the trait stays licensed so adding them needs no profile change. - Blind spots, stated: definitions a
used macro injects and the locals that call them; callback implementations as edges (handle_call/3under@behaviour GenServer— the analyzer recovers “implements callback” frominterfaceImplementationplus name/arity, as it does for Java overrides); message passing beyond the__MODULE__form; process topology at runtime (Registry,:viatuples, dynamic supervisors);.erlfiles in a mixed corpus (an Erlang profile is its own phase). A fileCode.string_to_quoted/2rejects (a syntax error, invalid UTF-8) is skipped and counted asunparsed; itsfilerecord is still written, with no entities. - The
--traceenrichment:mix codegraph.tracerecompiles a project that COMPILES under a compilation tracer and writes every call the compiler resolved after macro expansion (remote, local, imported, macros, struct expansions) with its site and caller; the extractor merges the file after its own pass. An event the baseline already wrote at the same site is the same fact; a new one is an edge with provenancegenerated, resolved through the same rules (an external callee lands on its stub module); a call to a function the module does not declare in source (__struct__/1, an injectedgreet/1) has no entity to land on and is counted asinjected, never invented; a Kernel binding iskernel. Keys never come from the trace — a model with and without it has the same entities, and everydeclarededge stays as it was. Phoenix (205 files): 16 797 events, 5 895 generated edges added to 14 314 declared ones. - Kernel.SpecialForms are variadic (
for,try,with,casetake any number of arguments) and are matched by name alone;x :: size(8)inside a binary is a specifier, not a call. A repeated name in one head (def f(mode, mode)pins the second) names one position only; the other is keyed by ordinal. - Audit (M15c, every model
validate-clean and byte-identical to core’s encoder, nodeps/read): elixir-lang/elixirlib(557 files, 27 061 entities / 78 564 edges, 17 s) resolves 80.8 % of 164 123 sites (52 140 Kernel), the rest counted — 30 629local_injected(its own tests’assert/testunderuse ExUnit.Case,use MixTest.Case), 2 659 map accesses, 736 dynamic dispatches, 460 remote calls to injected definitions; phoenixframework/phoenix (205 files, 5 828 / 14 314, 2.3 s) 76.7 % (6 616 injected, 1 900 map accesses); plausible/analytics (1 256 files, 16 369 / 43 661, 5 s) 58.2 % (46 793 injected —use PlausibleWeb, :controller, Ecto schemas, ExUnit; 14 401 map accesses). The injected share IS the macro ceiling the trace exists for: on Phoenix,mix xref graph(148 file-level compile and runtime edges) is witnessed 147 times by the baseline alone (the miss is a module reached only throughCode.ensure_loaded?), and the trace adds 5 895generatededges. - Measures (TMetrics, METAMODEL.md §3.8):
slocon the file, every module and every invocable — lines of its own span that are neither blank nor comment-only, a comment being what the compiler’s own parser reports as one: every non-blank line a heredoc, a sigil or a multi-line string spans is code, a#inside one included;cyclomaticon invocables: 1 + clause heads beyond the first +case/cond/with/receive/tryclauses beyond the first +if/unless+and/or/&&/||+rescue/catchclauses. A nestedfncontributes to its enclosing invocable: an anonymous function is not an entity. - The metrics pass (
codegraph-metrics, PLAN.md §18): big-code-analysis opens one space perdefclause, so a function of several clauses is joined by containment and carries the charged keys only (cognitive, exits, ABC) — nolloc, Halstead ormi, which do not add up over clauses.defprotocolanddefimplbodies,defguardanddefdelegatehave no space and carry none of the pass’s keys. The join’s witness (sloc= big-code-analysis’sploc) cannot hold over a heredoc: big-code-analysis leaves a heredoc’s text lines out ofploc, which METAMODEL’ssloccounts (Phoenix’smix_app_path:sloc19,ploc13).
Go — go
Edge kinds: import · interfaceImplementation · invocation · access · reference · embedding
| Kind | Required traits | Optional traits |
|---|---|---|
const | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
field | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
func | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment |
interface | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TWithImplements, TComment |
localVariable | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
method | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TAttachedTo, TSourceAnchor | TComment |
package | TNamed, TModule, TWithChildren | TComment |
parameter | TStructural, TTypedEntity, TChildOf | TNamed, TSourceAnchor |
struct | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TWithImplements, TComment |
typeAlias | TNamed, TType, TTypedEntity, TChildOf, TSourceAnchor | TComment |
Lexicon — what the vocabulary reads of this language
- Reserved words:
break·case·chan·const·continue·default·defer·else·fallthrough·for·func·go·goto·if·import·interface·map·package·range·return·select·struct·switch·type·var - Conventional names:
init·main·String·GoString·Error·Unwrap·Is·As·Len·Less·Swap·MarshalJSON·UnmarshalJSON·MarshalText·UnmarshalText·ServeHTTP - Technical words:
cfg·cmd·errs·internal·mu·pkg·srv·svc·wg
Notes
- Go has no inheritance: neither the TWithInheritances trait nor the inheritance edge kind belongs to this profile. Their absence is the profile’s statement about the language, not a missing feature.
- Interface satisfaction is implicit and structural: nothing in the source says a type implements an interface, so interfaceImplementation edges are always emitted with provenance “derived” and never “declared”. An analysis restricted to facts (declared edges) sees no implementation relation in Go at all.
struct { Base }andinterface { io.Reader }are embedding edges, not inheritance and not fields: the embedded type’s methods are promoted onto the outer type, which changes which interfaces the outer type satisfies but creates no subtype relation.- A method’s receiver drives TAttachedTo (the receiver type) while TChildOf stays the package where the method is written; value and pointer receivers attach to the same type entity and are separated by the id’s disambiguator.
- Multiple return values do not fit TTypedEntity’s single declaredType: the first result is the declaredType and every further result type is emitted as a reference edge.
- Short variable declarations (
:=), untyped constants andiotaruns leave TTypedEntity present with declaredType absent. - Calls through an interface value cannot be resolved statically: they are emitted against the interface method with provenance “dynamic-candidate” and the satisfying implementations as candidates.
- Invisible to static extraction: reflect, the plugin package, cgo, go:generate and go:linkname directives, and side effects of init() functions pulled in by blank imports (
import _ "…"), which are still emitted as ordinary import edges. - Build tags and GOOS/GOARCH constrained files mean a single extraction run sees one build configuration; entities excluded by the active tags are absent from the model.
Java — java
Edge kinds: import · inheritance · interfaceImplementation · invocation · access · reference · annotationUse · throws
| Kind | Required traits | Optional traits |
|---|---|---|
annotation | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
attribute | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment, TWithValue |
class | TNamed, TType, TWithInheritances, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
constructor | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TComment, TMetrics |
enum | TNamed, TType, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
interface | TNamed, TType, TWithInheritances, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
lambda | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TTypedEntity, TMetrics |
localVariable | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
method | TNamed, TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment, TMetrics, TWithValue |
package | TNamed, TModule, TWithChildren | TComment, TChildOf |
parameter | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
record | TNamed, TType, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
Lexicon — what the vocabulary reads of this language
- Reserved words:
abstract·assert·boolean·break·byte·case·catch·char·class·const·continue·default·do·double·else·enum·extends·final·finally·float·for·goto·if·implements·import·instanceof·int·interface·long·native·new·package·private·protected·public·return·short·static·strictfp·super·switch·synchronized·this·throw·throws·transient·try·void·volatile·while·true·false·null - Conventional names:
toString·equals·hashCode·clone·finalize·main·compareTo·iterator·hasNext·valueOf·serialVersionUID·readObject·writeObject·readObjectNoData·readResolve·writeReplace - Technical words:
annotation·bean·beans·ejb·jaxb·jdbc·jndi·jpa·pojo·servlet - Qualified kinds:
packageby.
Notes
- Measures (TMetrics, METAMODEL.md §3.8):
slocon every type and invocable — lines of its own span that are neither blank nor comment-only, counted by a scanner that knows string and text-block literals, so a"/*"in the source does not swallow the rest of the file.cyclomaticon invocables only: 1 + if / for / foreach / while / do / non-default case label / catch / ternary / short-circuit && and || / switch pattern guard. Purely syntactic, hence immune to the noClasspath resolution ceiling. A nested lambda or anonymous class does NOT contribute to its enclosing method: it is its own invocable and carries its own count. A type’s complexity is therefore not stored — it is the sum over its members, which the consumer computes. - A corpus is not a compilation unit. Spoon compiles every source root as a single JDT batch, so two Maven/Gradle modules that each declare the same fully-qualified name — legal, since neither module sees the other — collide as IProblem.DuplicateTypes. The extractor tolerates the collision rather than aborting (measured: one collision in a test helper aborted the extraction of a 4,972-file repository), but JDT keeps only the FIRST declaration it reached: the later file contributes no entity and no edge. Every dropped declaration is named on stderr, so a model of a multi-module corpus can legitimately be missing a type its sources contain.
- Spoon in noClasspath mode invents plausible fully-qualified names for unresolved types. Corpus membership is therefore decided by a whitelist of ids actually declared by the corpus (built in a first pass); anything else is emitted as an isStub entity. Never decide membership by package or name prefix — invented FQNs look exactly like real ones and a prefix filter would launder them into facts.
- Reflection is invisible: Class.forName, Method.invoke, proxies, Spring XML/annotation wiring, ServiceLoader/META-INF/services, and JNDI lookups produce no edge. The import/invocation graph of a reflection-heavy corpus is a lower bound.
- Lombok-generated members (getters, setters, @Builder, @Data constructors) are emitted with provenance “generated” when the expansion is visible to Spoon, and are missing entirely when it is not.
- An interface’s
extendslist is emitted as inheritance edges between types; interfaceImplementation is reserved for a class/enum/recordimplementsclause, always with provenance “declared”. - A
throwsedge is emitted per writtenthrowstatement whose static exception type resolves, anchored at the throw site — the evidence a guard clause (if (x) throw new E(...)) leaves in the model. A method’sthrowsCLAUSE stays a plain reference edge: it declares propagation, not a failure exit of this body. A rethrow (throw e;) targets the caught variable’s static type; a throw whose type Spoon cannot name is dropped and counted, like any unidentifiable target. - Static and on-demand (
import x.y.*) imports are folded to module-level import edges; the wildcard case names the package, not the individual types it brings in. - Overloads are distinguished by the id’s signature disambiguator, so an unresolved parameter type changes the id — a resolution failure shows up as a stub target, never as a merged entity.
- Java generics are erased in the model: type arguments are emitted as reference edges from the declaring entity, and declaredType carries the raw type.
- noClasspath resolution rate, measured (M2 audit): 100.0% on apache/commons-lang (263 files, 133478 type references, 0 unresolved) and 77.8% on spring-petclinic (30 files, 2189 references, 487 unresolved, all of them Spring and Jakarta types whose jars are absent). The rate is a property of the corpus’s DEPENDENCY SURFACE, not of the extractor: commons-lang is self-contained and depends on nothing but the JDK, which resolves against the runner’s own classpath. A jar that is not on the classpath cannot be resolved by any extractor, so a corpus with third-party dependencies has a structural ceiling well below 100% and a single cross-corpus target number is not meaningful. The stub discipline, not the resolution rate, is the property worth asserting.
- The resolution rate counts neither type variables nor
<nulltype>: neither names anything that could have a declaration, so counting them measures the corpus’s writing style. This is not cosmetic —<nulltype>, Spoon’s static type for thenullliteral, was ALL 1466 of commons-lang’s originally-reported unresolved references, making the headline number a function of how manyreturn null;statements the corpus contains. - In noClasspath Spoon promotes an unresolvable RECEIVER to a type in the enclosing package:
typeHint -> typeHint.with(...)andcm.setStatisticsEnabled(...)produced stub classes namedtypeHintandcminside the corpus’s own package on spring-petclinic. They are correctly stubbed (membership is the declared-id whitelist), and they are why the whitelist exists — but the stub set of a real corpus therefore contains a tail of entities named after local variables, and a stub count is not a count of external types. - An array type is not an entity, and neither is its component where a member is concerned.
xs.lengthandint[]::newdeclare their member onint[]/Money[]; folding that up names the component and states a fact the source never wrote. Such facts are dropped, not degraded — the dependency on the component is already carried by the written type reference that mentions it. - An anonymous class has exactly one id, the
#file:line:columnform pass 1 declared. Spoon names itOuter$N, which renders as a plausible nested-type id in the corpus’s own package; a reference resolved through that name gives one declared class two ids and launders the second into a stub. Type references must be resolved to their declaration before being named. - Spoon materializes the implicit constructor of an anonymous class with synthetic parameters named
$anonymousN. They are emitted (implicit members are, deliberately, so thatnew Foo()does not dangle), so the model contains a handful of parameter entities nobody wrote, carrying no anchor of their own — 5 of 15338 entities on commons-lang. - The metrics pass (
codegraph-metrics, PLAN.md §18) joins big-code-analysis spaces to entities on their anchors. Spoon starts a declaration AFTER its annotations and tree-sitter AT them; the pass bridges the two when nothing but annotation lines lies between (a LEADING join — most of a Spring corpus). It measures nothing on a lambda (big-code-analysis opens no space for one and charges its cognitive, exits and ABC to the enclosing method), on an implicit constructor (anchored on its class) or on an annotation element (no body): those carry none of the pass’s keys.
JavaScript — js
Edge kinds: import · inheritance · invocation · access · reference
| Kind | Required traits | Optional traits |
|---|---|---|
arrowFunction | TInvocable, TSourceAnchor | TComment, TChildOf, TWithChildren, TTypedEntity, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
class | TNamed, TType, TChildOf | TWithInheritances, TWithChildren, TSourceAnchor, TComment |
function | TInvocable | TNamed, TSourceAnchor, TComment, TChildOf, TWithChildren, TTypedEntity, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
method | TNamed, TInvocable, TChildOf | TSourceAnchor, TComment, TWithChildren, TTypedEntity, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses |
module | TNamed, TModule, TWithChildren | TSourceAnchor, TComment |
parameter | TNamed, TStructural, TChildOf | TTypedEntity, TSourceAnchor |
property | TNamed, TStructural, TChildOf | TTypedEntity, TSourceAnchor, TComment |
variable | TNamed, TStructural | TSourceAnchor, TComment, TChildOf, TWithChildren, TTypedEntity |
Lexicon — what the vocabulary reads of this language
- Reserved words:
await·break·case·catch·class·const·continue·debugger·default·delete·do·else·enum·export·extends·false·finally·for·function·if·import·in·instanceof·new·null·return·super·switch·this·throw·true·try·typeof·var·void·while·with·yield·implements·interface·let·package·private·protected·public·static - Conventional names:
constructor·toString·toLocaleString·valueOf·toJSON·[Symbol.iterator]·[Symbol.asyncIterator]·[Symbol.hasInstance]·[Symbol.toPrimitive]·[Symbol.toStringTag]·[Symbol.dispose]·[Symbol.asyncDispose] - Technical words:
cb·fn·promise·prototype - File kinds:
module - Qualified kinds:
moduleby/
Notes
- A module is a file:
TModule.definedInalways has exactly one entry (1-1 cardinality, unlike a Java package). - No
interfaceImplementationedge kind. JS has noimplementsclause and conformance is duck-typed, so it is never observable statically — the absence of the edge kind is the honest statement, not a gap to fill later with guesses. TTypedEntityis licensed on variables, parameters, properties and function returns, butdeclaredTypeis essentially always absent: JS annotates nothing. This is exactly why the attribute is optional rather than required by the trait. An extractor may populate it from JSDoc@type/@param/@returnswhen present; everything else is left absent rather than inferred.- Arrow functions and anonymous function expressions carry
TInvocablewithoutTNamed; their id disambiguator is(file, startLine), so they requireTSourceAnchor. const f = () => {}yields two entities: thevariablef and its childarrowFunction. Call sites resolve through the variable to the arrow, andinvocationedges target thearrowFunction.- Dynamic
import(expr)andrequire(expr)with a computed, template-literal or variable path are unresolvable: noimportedge is emitted. Only statically literal specifiers produce edges. - CommonJS/ESM interop is a blind spot:
module.exports = X,exports.a = ...,__esModuleinterop shims and conditionalpackage.jsonexports mean the same physical file can be reached under several specifiers. Ids are keyed on the resolved file path, so a resolver failure produces a stub module rather than a wrong merge. - Monkey patching (
Obj.prototype.m = fn,Object.assign(target, mixin),Object.defineProperty) adds members at runtime. No method or property entity is created for them; the assignment surfaces only asaccessandreferenceedges at the patch site. - Duck typing means a call
x.m()on an unannotated receiver has no resolvable target. Such invocations are either omitted or emitted with provenancedynamic-candidateand every same-named method in the corpus listed incandidates. - Computed member access
o[k]and dynamic property names giveaccessedges whose target is unknown; they are omitted rather than guessed at a single field. thisrebinding (call/apply/bind, arrow lexicalthis, extracted methods) breaks receiver-based resolution; edges through a reboundthisare not reconstructed.- Getters/setters are emitted as
methodentities; reads and writes of the underlying value at call sites areaccessedges, so a property read that runs code is visible as access, not invocation. - Class fields and object literal members share the
propertykind; object literals used as namespaces therefore appear as avariablewithpropertychildren.
PHP — php
Edge kinds: import · inheritance · interfaceImplementation · invocation · access · reference · traitUsage · fileInclude
| Kind | Required traits | Optional traits |
|---|---|---|
class | TNamed, TType, TWithInheritances, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment |
closure | TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TTypedEntity |
codeFile | TNamed | TWithChildren, TSourceAnchor, TComment |
constant | TNamed, TStructural, TChildOf, TSourceAnchor | TTypedEntity, TComment |
enum | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TWithImplements, TTypedEntity, TComment |
function | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment |
interface | TNamed, TType, TWithInheritances, TWithChildren, TChildOf, TSourceAnchor | TComment |
method | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment |
namespace | TNamed, TModule, TWithChildren | TChildOf, TComment |
parameter | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
property | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
trait | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TComment |
Lexicon — what the vocabulary reads of this language
- Reserved words:
__halt_compiler·abstract·and·array·as·break·callable·case·catch·class·clone·const·continue·declare·default·die·do·echo·else·elseif·empty·enddeclare·endfor·endforeach·endif·endswitch·endwhile·eval·exit·extends·final·finally·fn·for·foreach·function·global·goto·if·implements·include·include_once·instanceof·insteadof·interface·isset·list·match·namespace·new·or·print·private·protected·public·readonly·require·require_once·return·static·switch·throw·trait·try·unset·use·var·while·xor·yield·__CLASS__·__DIR__·__FILE__·__FUNCTION__·__LINE__·__METHOD__·__NAMESPACE__·__PROPERTY__·__TRAIT__·true·false·null - Conventional names:
__construct·__destruct·__call·__callStatic·__get·__set·__isset·__unset·__sleep·__wakeup·__serialize·__unserialize·__toString·__invoke·__set_state·__clone·__debugInfo - Technical words:
arr·closure·facade·middleware·php - File kinds:
codeFile - Qualified kinds:
codeFileby/·namespaceby\
Notes
- fileInclude (include/require/_once) is the metamodel’s only file-to-file dependency; its endpoints are
codeFileentities. A file both declares entities and includes other files, so the same file appears as a codeFile node and as theanchor.fileof the declarations it contains. - include/require paths built from variables, concatenation, constants or DIR arithmetic are invisible: only literal paths produce a fileInclude edge.
use TraitX;is a traitUsage edge Type -> Trait and the trait’s members are NEVER flattened into the using class. Conflict resolution (insteadof,as, visibility changes) and abstract trait members are not represented; a resolver must read the trait’s own children.use Foo\Bar;statements are per-file aliases, not namespace-level facts: emit them as import edges and setsourceFileto the declaring file so several files sharing a namespace stay distinguishable.- __call/__callStatic/__get/__set/__invoke are dynamic dispatch: invocation and access edges through magic methods carry provenance ‘dynamic-candidate’ with a candidates list, or are absent when no candidate can be named.
- Variable variables ($$name), variable functions ($fn()), call_user_func,
new $classand string class names resolve only for literal arguments; everything else is invisible. - eval(), conditional declarations inside if/function bodies, and class_alias() are not modelled.
- Single inheritance for classes (at most one inheritance edge); interfaces may extend several, so N edges are legal there.
- Gradual typing: parameter, property and return type declarations are optional, so TTypedEntity.declaredType is often absent. Docblock types (@var, @param) are not authoritative and must not be promoted to declaredType.
- Namespaces are 1-N with files: TModule.definedIn lists every file declaring into the namespace. A file with no
namespacestatement declares into the global namespace.
Python — python
Edge kinds: import · inheritance · interfaceImplementation · invocation · access · reference
| Kind | Required traits | Optional traits |
|---|---|---|
attribute | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
class | TNamed, TType, TWithInheritances, TWithChildren, TChildOf, TSourceAnchor | TWithImplements, TWithInvocations, TWithAccesses, TComment |
decorator | TAttachedTo, TWithInvocations, TSourceAnchor | TNamed, TChildOf, TComment |
function | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment |
lambda | TInvocable, TWithParameters, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TTypedEntity |
method | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TAttachedTo, TComment |
module | TNamed, TModule, TWithChildren | TChildOf, TSourceAnchor, TComment |
parameter | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
variable | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor, TComment |
Lexicon — what the vocabulary reads of this language
- Reserved words:
False·None·True·and·as·assert·async·await·break·class·continue·def·del·elif·else·except·finally·for·from·global·if·import·in·is·lambda·nonlocal·not·or·pass·raise·return·try·while·with·yield - Conventional names:
self·cls·__init__·__new__·__del__·__repr__·__str__·__bytes__·__format__·__hash__·__bool__·__eq__·__ne__·__lt__·__le__·__gt__·__ge__·__len__·__iter__·__next__·__aiter__·__anext__·__reversed__·__contains__·__getitem__·__setitem__·__delitem__·__missing__·__getattr__·__getattribute__·__setattr__·__delattr__·__dir__·__get__·__set__·__delete__·__set_name__·__call__·__enter__·__exit__·__aenter__·__aexit__·__await__·__init_subclass__·__class_getitem__·__post_init__·__add__·__sub__·__mul__·__matmul__·__truediv__·__floordiv__·__mod__·__pow__·__neg__·__pos__·__abs__·__invert__·__and__·__or__·__xor__·__lshift__·__rshift__·__int__·__float__·__index__·__round__·__copy__·__deepcopy__·__reduce__·__getstate__·__setstate__·__slots__·__all__·__name__·__main__·__doc__·__dict__ - Technical words:
dict·kwargs·metaclass·py·tuple - Qualified kinds:
moduleby.
Notes
- A module is a file: TModule.definedIn holds exactly one path (1-1 cardinality). A package is the module of its init.py.
- Multiple inheritance emits N inheritance edges from the class. MRO (C3 linearization) order is NOT represented: the edge set is unordered, so method-resolution questions cannot be answered from the model.
- Protocols (PEP 544) are structural, never declared: conformance yields interfaceImplementation edges with provenance ‘derived’. Explicit
class X(Protocol)subclassing is a plain declared inheritance edge instead. - Decorators that synthesize members (dataclasses, attrs, ORM/metaclass bases) emit the members and their edges with provenance ‘generated’; a pre-expansion extraction sees none of them.
- Duck typing: a call whose receiver type is not statically known resolves to provenance ‘dynamic-candidate’ with a candidates list, or is absent entirely.
- Monkey patching (assigning functions or attributes onto classes and modules at runtime) is invisible to static extraction; the model shows the original definition site only.
- getattr/setattr/hasattr and importlib.import_module/import resolve only for literal string arguments; computed names produce no edge.
- Type hints are optional and may be deferred strings (PEP 563 /
from __future__ import annotations), so TTypedEntity.declaredType is frequently absent even where the trait is present. from x import *yields a module-level import edge only; the individual names it binds cannot be attributed.- Conditional and
if TYPE_CHECKING:imports are emitted like any other import — this profile declares no Space, so type-only dependencies are indistinguishable from runtime ones.
Ruby — rb
Edge kinds: import · inheritance · traitUsage · invocation · access · reference · throws
| Kind | Required traits | Optional traits |
|---|---|---|
class | TNamed, TType, TWithInheritances, TWithChildren, TChildOf, TSourceAnchor | TAttachedTo, TComment, TMetrics, TWithInvocations, TWithAccesses |
constant | TNamed, TStructural, TChildOf, TSourceAnchor | TWithValue, TComment, TWithInvocations, TWithAccesses |
field | TNamed, TStructural, TChildOf, TSourceAnchor | TWithValue, TComment |
file | TNamed, TModule, TWithChildren | TSourceAnchor, TComment, TMetrics, TWithInvocations, TWithAccesses |
method | TNamed, TInvocable, TWithChildren, TWithParameters, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TWithLocalVariables, TComment, TMetrics |
module | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TAttachedTo, TComment, TMetrics, TWithInvocations, TWithAccesses |
parameter | TStructural, TChildOf | TNamed, TSourceAnchor, TWithValue |
Lexicon — what the vocabulary reads of this language
- Reserved words:
__ENCODING__·__LINE__·__FILE__·BEGIN·END·alias·and·begin·break·case·class·def·defined?·do·else·elsif·end·ensure·false·for·if·in·module·next·nil·not·or·redo·rescue·retry·return·self·super·then·true·undef·unless·until·when·while·yield - Conventional names:
initialize·initialize_copy·to_s·to_str·to_a·to_ary·to_h·to_hash·to_proc·inspect·hash·eql?·==·===·=~·!~·!=·<=>·<·>·<=·>=·+·-·*·/·%·**·<<·>>·&·|·^·~·!·+@·-@·[]·[]=·each·coerce·method_missing·respond_to_missing?·respond_to?·included·extended·inherited·prepended·method_added·const_missing - Technical words:
accessor·attr·concern·concerns·ext·gem·gemspec·klass·lambda·proc·rake·rakefile - File kinds:
file - Qualified kinds:
fileby/·classby::·moduleby::·constantby::
Notes
- The module is the file (the TypeScript and Elixir rule):
TModule.definedInhas exactly one entry, and every entity’s module is the file it is written in.classandmoduleare types below it, named and keyed by their fully qualified constant path (Acme::Order) —::is part of the name, never a separator of the key. A type written inside the body of a type that is an entity in the same file is its child; otherwise it is a child of the file. A value constant is keyed the same way (Acme::Order::MAX_LINES), a child of the type whose body assigns it. - Ruby classes are open, so a class or a module is ONE ENTITY PER DECLARING FILE (the TypeScript declaration-merging rule): every body of
Acme::Orderin one file folds into one entity anchored at the first, and a second file reopening it declares a second entity with the same name. A reference to the constant targets the PRIMARY declaration, chosen deterministically — the one that writes a superclass, else the first in canonical file order — and every other declaration isattachedToit: containment says where a body is written, attachment which type it belongs to. A method is looked up across every body, and lands on the one in the calling file when there is one. - A monkey patch is a reopening by construction: the VM defined
Stringbefore any file ran, so a corpusclass Stringcannot be its definition. It is a declared entity in its file,attachedTothe stub<ruby>/String, and a reference toStringanywhere still names the VM’s — the patch file does not become the hub every string in the corpus points at. A method the patch adds IS found, before the VM’s own ("".blank?lands on the corpusString.blank?). The one corpus this misreads is a library that itself implements a constant of the core table (rubygems’ ownGem): its references land on the stub. A library’s class the corpus reopens (class ActiveRecord::Base) has no such table to consult and IS the declaration the corpus knows. - A wrapper body is not a declaration:
module Acme; class Order; end; endandclass Acme::Order; endare two spellings of one thing, and both yieldAcme::Orderalone. Amoduleorclassbody with no superclass whose statements are all class or module declarations —Point = Struct.new(…)being one — declares nothing of its own in that file: it is lexical scope, Ruby’s spelling of a package statement, so a namespace reopened in fifty files is not fifty entities. An empty body (class NotFound < StandardError; end,module Marker; end) IS a declaration. A constant path no file gives a body to has no entity, and a reference to it is dropped and counted (namespace-only) — UNLESS the corpus itself proves the body exists elsewhere: a message is sent to it that no bare module answers (Rails.env, where the corpus only writesclass Rails::Patch), or a class inherits from a path nothing in the corpus opens. That namespace is a library’s, a stub below<gems>; what the corpus declares under it stays the corpus’s. Point = Struct.new(:x, :y) do … end,Coord = Data.define(:lat),Handler = Class.new(Base)andMixin = Module.neware types named by the constant, the block their body: itsdefs are their methods.Struct.newandData.define— as a superclass too,class Money < Struct.new(:cents)— yield aninheritanceedge to<ruby>/Struct/<ruby>/Datawith provenancegenerated: the source writes no< Struct, the language defines it. The superclass given toClass.newis written, hencedeclared. Each member is afield.- Key components are percent-encoded:
/,#and%in every path segment and name (def /(other)is a method named/, keyedMoney.%2F), and<in a path segment, so that no walked path can spell a reserved module. Ruby has no overloading — a name is one method per type — so a method carries no arity;def self.callanddef callcoexist in one class, and the singleton method alone carries the disambiguatorself, the keyword the source writes. A method written twice in one file is one entity, anchored at the first. A parameter is keyed#param:<name>below its method, by 1-based ordinal when it has no name or repeats one. signatureis the method as it would be called:total(currency, [precision], *rest, tax:, [rounding:], **opts, &blk)— brackets mark a parameter with a default, whose value is not part of the signature; a singleton method is prefixedself.. The default itself rides as the parameter’svaluewhen it is a constant expression: a string, a number,true/false/nil, an array of those, a frozen literal ("EUR".freeze); a symbol, a hash, a range and a regexp asunevaluatedwith the text written; a constant naming a type as atypeliteral. A default or an initializer that calls carries no value at all.- A
fieldis an instance variable, and theattr_reader/attr_writer/attr_accessorthat exposes it is the SAME entity, named@totaland anchored at theattr_*when there is one, else where the variable is first written.@totalin a body and a call of the accessor (total,self.total = x,order.totalon a typed receiver) areaccessedges — the C# property rule: the accessor is not an entity. An instance variable belongs to the object, so a subclass reading@totalreads the field of the root-most corpus ancestor that has it.@xin a class body or a singleton method is the class’s own variable and carries theselfdisambiguator;@@xis a field of the type. A Struct member is a field named like the variable it stands for (@cents). - Corpus membership is a whitelist of the constant paths the roots declare or assign, never a name-prefix test. Every other type is a stub below one of two reserved modules:
<ruby>for a constant the VM defines before any file runs (the core classes and modules, from a table distilled from RBScore/at a pinned version and embedded in the extractor — the header names that version), and<gems>for a constant the roots do not declare and the VM does not define: a library, whichever one. The standard library is in<gems>on purpose — nearly all of it is default or bundled gems, the set moves with every Ruby release, and a model’s keys must not. A<gems>stub is aclasswhen a site inherits from it, instantiates it or raises it, and amoduleotherwise — every class is one; it may also be a value constant of that library, which nothing here can tell. That kind is the evidence ONE corpus holds, so two corpora can disagree on a stub (Loggerinstantiated here, only named there): validating their union reports it as a duplicate-id conflict. A stub has no members: a call on a library’s type lands on the stub. require,require_relative,loadandautoloadwith a literal argument areimportedges from the file, wherever they are written.require_relativenames a path: the file it resolves to, or a stub module keyed by that path when nothing is there; the Ruby 1.8 spellings fold to it (File.expand_path("../x", __FILE__),File.join(File.dirname(__FILE__), "x"),"#{__dir__}/x").require "acme/order"is searched on the load roots the layout implies — every directory namedlib,specortest, then the root — nearest to the requiring file first, and that resolution is a fact about the repository rather than the language, counted on its own. A feature no corpus file answers is a stub module keyed by the feature as written below<gems>(<gems>%2Fnokogiri,<gems>%2Fnet%2Fhttp). Any other computed argument is dropped and counted.- A Zeitwerk application writes no
require: the constant reference IS the import, turned into a load at run time. So a constant that resolves to a declaration in another file — a type or a value, in a header or in a body — yields ONEimportedge per (file, target file), provenancederived, anchored at the first such reference, and none when the file already imports that file explicitly. Without it the import layer, the one layer comparable across languages, is empty for every Rails application; with it, a consumer that needs written facts only filters ondeclared. - A constant is resolved as Ruby resolves it: each enclosing scope from the innermost out (
Module.nesting— a compactclass A::Bopens ONE scope,A::B, and does not seeA), then the ancestors of the innermost scope (a constant of the superclass or of an included module), then the top level;::Xis absolute;A::BresolvesAthat way, thenBinAand in whatAinherits from (Child::LIMITisBase::LIMITwhen Base assigns it) — never at the top level. A compact declaration READS what it is written under:class Admin::Usersis areferencefromAdmin::UserstoAdminwhereAdminis a type, since the name must exist for the class to open; a wrapper (module Admin; class Users) creates what it opens and reads nothing. The header of a declaration (< Base,include M) is read by the lexical scopes alone. A constant read is areferenceto the type it names, or anaccessto the value constant it names;A::B::Cis one read, not three. A read of a VM value (ARGV,ENV) is the language: resolved, counted, no edge. class A < Bis aninheritanceedge whenBis a constant;< Class.new(Base),< ActiveRecord::Migration[7.0]and every other computed superclass is dropped and counted — the constants it names are still referenced, from the class.include M,extend Mandprepend Mwritten in a type’s body (orinclude Minside itsclass << self) aretraitUsageedges from the type, one per argument, and the edge does not say which of the three was written;extend selfis not a relation. What anincludedhook gives whatever includes the module is read where the idiom is literal:base.extend(M),base.include(M)and theirsendspellings indef self.included(base); and, in a module that writesextend ActiveSupport::Concern, itsClassMethodsmodule,class_methods do … end— the BODY of that module, an entity anchored on the block — and the mixins written directly inincluded do … end. NotraitUsageis written for it, since the includer writes none: the lookup goes through the hook, and what it finds there is an inference (below). A mixin applied any other way — a computed argument, a hook that calls a helper — is invisible to the parser;--traceadds it.- A call is an
invocationwhen what it is sent to has a type the source states, looked up as Ruby would: prepended modules, the type, its mixins last-included first, its superclass’s — and on the class side its singleton methods, what it extends, its superclass’s.declared: an implicit or explicitself,self.class.x,Foo.xon a constant,Foo.new(…).x,super, andnew(…).xwhereselfis the class.Foo.new— and a barenewin a class method — is an invocation ofinitialize.module_functionandextend selfmake an instance method answer on the module; analias_methodoraliasanswers with the method it names;send(:x)with a literal name is a call like any other, andmethod(:x)areferenceto the method. A top-leveldefis a method of every object. A block is assumed to keepself, aseachandmapdo: aninstance_eval-style DSL block is not detected. derivedsays the receiver’s type was inferred: a local every assignment of which in its body isFoo.new(…)(flow-insensitively — one other assignment anywhere untypes it, and a parameter types nothing), and an instance variable every assignment of which in the type isFoo.new(…), the@repo ||= Repo.newidiom included. An accessor on such a receiver is aderivedaccess.derivedalso says the METHOD was reached through anincludedhook:audited :totalin a class that includes a concern whoseclass_methodsdefineaudited— the receiver is written, the link from the class to that module is not.- What every object answers (
puts,raise,freeze— Object, Kernel, BasicObject), what every class answers in a class-side call (new,attr_accessor,private,include— Class, Module), and any method of a literal ("x".upcase,[1].map) are the LANGUAGE: resolved, counted, and never an edge. A method the corpus inherits from a core class it names is a dependency on it —upcasein a subclass of String is aninvocationof<ruby>/String,between?of<ruby>/Comparable— and a call on a core class is an invocation of that class as written (File.readlands on<ruby>/File, thoughreadis IO’s; so doesTime.zone, which some library added). - A call nothing the corpus or the VM defines answers, in a type with exactly ONE library that could answer, is an
invocationof that library’s stub with provenancederived— an inference by elimination, wherehas_manyandwhereland in an ActiveRecord model. A library that could answer is a library superclass, a library module mixed in — and, on the class side, a library module merely INCLUDED, whose hook may have extended the class:validatesin a model that includes ActiveModel::Validations is that module’s, not ActiveRecord::Base’s. Two of them is dropped and counted asambiguous-ancestor; none, asunbound-self-call(unbound-methodon a named receiver). A type that defines methods nodefwrites proves nothing by not declaring one — on the side they are defined for:def method_missingand adefine_methodwith a computed name in a class body cloud its INSTANCES,def self.method_missinganddefine_singleton_methodthe class itself; a miss on that side isunbound-dynamic. - A receiver no parser can type — a parameter, a block argument, the result of a call — is the stated ceiling: dropped and counted as
untyped-receiver, never guessed, candidate generation being the analyzer’s. An operator on such a receiver (a + b,x[0],!done) is counted apart and kept out of the resolution rate: it is the language far more often than not.--explain-droppedlists every dropped site with its reason, file and line, and what decided it where something did: the type that defines methods at run time for anunbound-dynamic, the libraries that could answer for anambiguous-ancestor. raise Foo,raise Foo, "msg"andraise Foo.new(…)arethrowsedges to Foo, beside thereferencethe constant read also is;raise "text"throws<ruby>/RuntimeError.raise errorre-raises a value whose class the source does not say: dropped and counted.- A
defbelongs to the type whose body it is written in — adefinside a block (included do … end) too, since a block does not change where a constant or a method is written, a concern’sclass_methods do … endalone excepted (it is the body ofClassMethods); at a file’s top level it is a child of the file.def self.xand adefinsideclass << selfare singleton methods of the enclosing type.define_method(:x) { … }with a literal name is a method like adef, its block the body.def obj.x, adefinsideclass << obj, and a type whose name is computed (class self::Foo) or written inside a singleton class have no key to carry them: dropped and counted, their constants still read, their calls not. - An edge comes from the narrowest entity that encloses its site: a method, else the constant whose initializer it is in, else the class or module body, else the file. A class body is code (
has_many :lines,attr_reader), a file’s top level too (a Rakefile, a gemspec) — hence the optionalTWithInvocations/TWithAccesseson those kinds, declared when the entity is the source of such an edge. A call is anchored at the line its name is written on, not across the block it is given. - Blind spots, stated: methods no
defwrites (define_methodwith a computed name,method_missing, aclass_evalof a string in a loop, the accessorsdelegate,def_delegatorsand ActiveRecord generate); mixins applied at run time other than through a literalincludedhook; a test framework’s DSL (describe,it,expect,let— a spec directory is the bulk of what a Rails application leaves unbound);send,public_send,const_get,constantizewith a computed name;class_evalandinstance_eval; ERB, Haml and Slim templates, which are not Ruby files; a file’s top-level DSL (gemin a Gemfile,taskin a Rakefile,runin aconfig.ru) — evaluated by a library against its own object, so unbound here. Visibility (private,protected) is not modelled. - Walked:
*.rb,*.rake,*.gemspec,*.ru, the extensionlessRakefile,Gemfile,Guardfile,Capfile,Vagrantfile,Brewfile,Dangerfile,Thorfile, and any other extensionless file whose first line is arubyshebang — under the roots and outsidevendor,node_modules,tmpand dot-directories (a root named on the command line is walked whatever it is called). Prism recovers from syntax errors and honours encoding magic comments, so a file with errors is still extracted from the tree the parser recovered, and named on stderr; a name that is not valid UTF-8 is written with U+FFFD and counted. - Comments (
TComment): the#block written directly above a class, a module, a method or a constant, one string, magic comments (frozen_string_literal) and tool directives (rubocop:) looked through. Measures (TMetrics, METAMODEL.md §3.8):slocon the file, every type and every method — lines of its own span that are neither blank nor comment-only, a heredoc’s text being code and what follows__END__data;cyclomaticon methods: 1 +if/elsif/unless/ternary +while/until/for+ eachwhen/in+ eachrescueclause and modifier +&&/||/and/or. A block contributes to its enclosing method: it is not an entity. - The metrics pass (
codegraph-metrics, PLAN.md §18): big-code-analysis’s Ruby grammar opens a space perclass,moduleanddef, so every file, type and method joins exactly and the join’s witness holds —slocequals itsplocon each (106 of 106 on the fixture).Point = Struct.new(…) do … end,Handler = Class.new(Base) do … endand a concern’sclass_methods do … endare blocks to the grammar: no class space of their own, and none of the pass’s keys. --trace <file>merges whatextractors/ruby/trace/codegraph_trace.rbwrote from INSIDE the running program — the one thing the extractor never does is run corpus code, so the trace is a separate step, the user’s, in a project that still boots. It names constants, files and lines, never a key: no entity is added and no edge of the source changes. A mixin the VM holds and no line writes is atraitUsagewith provenancegenerated, anchored on the declaration of the type that has it. A call the program made at a site the baseline left unresolved is ONEinvocationwith provenancedynamic-candidate,candidateslisting every method that site was seen to reach andtothe first of them — what ran once, not what must run. A method the VM holds on a line that writes nodefhas no entity and gets none: it is counted.- Audited (M18c) on rake 13.2.1, rubygems 3.6.2, Mastodon 4.3.0 and rails 8.0.2 — 100, 794, 2 541 and 3 334 files, every model
validate-clean, rails in 4.7 s. Resolution rate 77.8 %, 54.4 %, 31.2 % and 57.0 % of the sites; Mastodon withoutspec/is 62.1 % (55 000 of its unbound calls are RSpec’s DSL), rails’ ownlibandapp59.4 %. Four fifths of what stays unresolved in application code isuntyped-receiver— the ceiling of any reader that does not run the code. ORACLES: Packwerk 3.3.1’s reference extraction and ConstantResolver over Mastodon’s autoload paths resolve 4 335 cross-file constant references inapp/andlib/; the model has the same constant in the same file for 4 289 of them (98.9 %) — the 45 others are amodule AdminWRAPPER, which Packwerk counts as a reference and the wrapper rule does not, and one an inherited constant Packwerk places by file name. The model names 346 more that Packwerk cannot: declarations outside the autoload paths. Run under its own tests in a container, rake’s VM holds a method on the same line for 1 006 of the 1 012 methods the model declares in the 83 files it loaded (the six others: definitions under a platform or capabilityif, adefinside a method that never ran, methods of anonymous classes), and 200 methods the model has none for — 199 written by the metaprogramming loops of three files (class_evalof a string,define_methodover a list), one theelsebranch of a platform test. Of the 2 829 calls observed during that run, 1 533 are edges the baseline wrote and 879 land on sites it had left untyped. The core-table misreading, measured on rubygems: 39 corpus bodies of 20 VM types (Gemalone 12), and 3 802 of 71 197 edges landing on the VM’s stub of a type that corpus itself implements.
Rust — rust
Edge kinds: import · interfaceImplementation · invocation · access · reference
| Kind | Required traits | Optional traits |
|---|---|---|
const | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
crate | TNamed, TModule, TWithChildren | TComment |
enum | TNamed, TType, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment |
field | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment |
function | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment |
impl | TWithChildren, TAttachedTo, TSourceAnchor | TChildOf, TComment |
localVariable | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor, TComment |
method | TNamed, TInvocable, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TTypedEntity, TChildOf, TSourceAnchor | TComment |
module | TNamed, TModule, TWithChildren, TChildOf | TSourceAnchor, TComment |
parameter | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor |
struct | TNamed, TType, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment |
trait | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TComment |
typeAlias | TNamed, TType, TTypedEntity, TChildOf, TSourceAnchor | TComment |
Lexicon — what the vocabulary reads of this language
- Reserved words:
as·async·await·break·const·continue·crate·dyn·else·enum·extern·false·fn·for·if·impl·in·let·loop·match·mod·move·mut·pub·ref·return·self·Self·static·struct·super·trait·true·type·unsafe·use·where·while·abstract·become·box·do·final·gen·macro·override·priv·try·typeof·unsized·virtual·yield - Conventional names:
main·fmt·drop·clone·clone_from·eq·ne·partial_cmp·cmp·deref·deref_mut·into_iter·from_str·try_from·try_into·as_ref·as_mut·borrow_mut·index_mut·default·poll - Technical words:
arc·buf·cell·ffi·iter·ok·opt·rc·refcell·rs·rx·sys·tx·vec
Notes
- Rust has no inheritance: TWithInheritances and the
inheritanceedge kind are absent from this profile by design, not by omission. Supertrait bounds (trait A: B), generic bounds and where-clauses arereferenceedges. - The impl block is reified (METAMODEL.md §7): an anonymous entity with [TWithChildren, TAttachedTo, TSourceAnchor] and no TNamed,
attachedTothe implementing type, its methods as children. The interfaceImplementation edge Type -> Trait takes that block as its anchor. Inherentimpl Typeblocks carry no such edge but still own their methods. - Imports exist at two levels:
usepaths inside the module tree and crate dependencies declared in Cargo.toml. Both areimportedges; the level is read from the endpoints’ kinds (module vs crate), never from the id string. - Modules are N-N with files: TModule.definedIn lists every file contributing to a module — inline
mod x { }blocks put several modules in one file, while mod.rs plus sibling files (and #[path] attributes) spread one module tree over many. - Blanket impls (
impl<T: Bound> Trait for T) and derived trait impls yield interfaceImplementation edges with provenance ‘derived’: the concrete implementing types are computed by the analysis, not written in the source. - #[derive(…)], macro_rules! and proc-macro expansion produce entities and edges with provenance ‘generated’. A pre-expansion extraction sees none of them; a post-expansion one must anchor them at the invocation site.
dyn Traitobjects, generic calls and function pointers resolve to provenance ‘dynamic-candidate’ with the known impls as candidates.- cfg-gated code (
#[cfg(...)], feature flags, target attributes) may be absent from any single extraction: a model reflects exactly one configuration, so two extractions of the same crate can legitimately differ in entity set. - Trait default method bodies live in the trait, not in the impl blocks that inherit them; their invocation and access edges originate from the trait’s method, and an impl that does not override the method has no child for it.
- Enum variants are not reified as a kind of their own: the enum is the type-level unit, variant payload types appear as
referenceedges from it, and named variant fields arefieldchildren. staticitems map to theconstkind. Tuple-struct fields are named by their position (“0”, “1”, …). Shadowedletbindings produce several localVariable entities disambiguated by (file, startLine).- The
selfreceiver is not emitted as a parameter; the receiver relation is the impl block’s attachment.
TypeScript — ts
Declaration spaces: abstractClass, arrowFunction, class, enum, function, interface, method, module, namespace, parameter, property, typeAlias, variable.
Edge kinds: import · inheritance · interfaceImplementation · invocation · access · reference · annotationUse · throws
| Kind | Required traits | Optional traits |
|---|---|---|
abstractClass | TNamed, TType, TWithInheritances, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics, TWithInvocations, TWithAccesses |
arrowFunction | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TTypedEntity, TComment, TMetrics |
class | TNamed, TType, TWithInheritances, TWithImplements, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics, TWithInvocations, TWithAccesses |
constructor | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TComment, TMetrics |
enum | TNamed, TType, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
function | TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TNamed, TTypedEntity, TComment, TMetrics |
interface | TNamed, TType, TWithInheritances, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics |
method | TNamed, TInvocable, TWithChildren, TWithParameters, TWithLocalVariables, TWithInvocations, TWithAccesses, TChildOf, TSourceAnchor | TTypedEntity, TComment, TMetrics |
module | TNamed, TModule, TWithChildren | TSourceAnchor, TComment, TMetrics, TWithInvocations, TWithAccesses, TWithLocalVariables |
namespace | TNamed, TWithChildren, TChildOf, TSourceAnchor | TComment, TMetrics, TWithInvocations, TWithAccesses, TWithLocalVariables |
parameter | TNamed, TStructural, TTypedEntity, TChildOf | TSourceAnchor, TWithValue |
property | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment, TWithChildren, TWithValue, TWithInvocations, TWithAccesses |
typeAlias | TNamed, TType, TChildOf, TSourceAnchor | TTypedEntity, TComment, TMetrics |
variable | TNamed, TStructural, TTypedEntity, TChildOf, TSourceAnchor | TComment, TWithChildren, TWithValue, TWithInvocations, TWithAccesses |
Lexicon — what the vocabulary reads of this language
- Reserved words:
await·break·case·catch·class·const·continue·debugger·default·delete·do·else·enum·export·extends·false·finally·for·function·if·import·in·instanceof·new·null·return·super·switch·this·throw·true·try·typeof·var·void·while·with·yield·implements·interface·let·package·private·protected·public·static - Conventional names:
constructor·toString·toLocaleString·valueOf·toJSON·[Symbol.iterator]·[Symbol.asyncIterator]·[Symbol.hasInstance]·[Symbol.toPrimitive]·[Symbol.toStringTag]·[Symbol.dispose]·[Symbol.asyncDispose] - Technical words:
cb·fn·promise·prototype - File kinds:
module - Qualified kinds:
moduleby/
Notes
- This is the only profile that populates
Entity.space(METAMODEL.md §1.4). Type-space only (space: ["type"]):interface,typeAlias, aconst enum(its members are inlined at emit, leaving no runtime entity), and anamespacethat declares only types. Both spaces (["type", "value"]):class,abstractClass, a plainenum, and anamespacethat declares at least one value. Value-space only (["value"]):module,function,arrowFunction,constructor,variable,parameter. Amethodorpropertyis["type"]when written in aninterfacebody — it exists only for the checker, and an access to it is a dependency on the interface’s SHAPE — and["value"]when written in a class or an object literal. - A dependency whose target is type-space only is ERASED at runtime: it exists for the type checker and leaves nothing in the emitted JavaScript. Runtime, bundling and deployment analyses should therefore filter edges whose
toresolves to aspace: ["type"]entity; architectural coupling and design analyses should keep them, since the design dependency is real. Because the distinction is per-analysis, the model always stores both and never pre-filters. import type { X }and inlinetypespecifiers produce ordinaryimportedges; they are erased at runtime and are recognized by the space of their target, not by a separate edge kind.- The module is the file:
TModule.definedInalways has exactly one entry, and every entity’s module is the file it is written in — a script file’s global declarations included (containment is where a thing is written, CLAUDE.md invariant 5; there is no<global>module). Anamespaceblock is a child entity of its file or enclosing namespace, never a module. An ambientdeclare module "pkg"block in a corpus.d.tsIS a module entity, keyed by the quoted name, withisStub: false— the corpus declares it, and an import ofpkgresolves to it. - Key components are percent-encoded: the file path that names a module is made of
/, whichrenderIdreserves, so every path segment and every non-identifier name entering a key encodes exactly/as%2F,#as%23,%as%25and — in names only, where.separates nesting —.as%2E. Identifiers contain none of the four and are written as-is, so the encoding is injective and reversible; the module entity’snameis the unescaped path. A#privatemember is thereforeFoo.%23secret, and a string-literal member"a.b"isFoo.a%2Eb. - Declaration merging yields one entity PER DECLARING FILE (the module is the file):
interface Orderin two files is two entities, and two declarations in one file are one entity anchored at the first. An edge to a merged symbol targets the declaration that owns the referenced member; an edge to the merged container itself targets its first declaration in canonical file order — a deterministic choice, not a claim that the others do not exist. A module augmentation of an external type (declare module "express" { interface Request { user: User } }) makes its members declared entities parented by the stub type. - Structural (non-nominal) typing: a class conforms to an interface without any
implementsclause.interfaceImplementationtherefore carries provenancedeclaredonly for an explicitimplements; conformance computed by shape comparison isderived, is never computed by the extractor, and must never be merged with the declared edges. Any analysis wanting facts filters ondeclared. - Corpus membership is a whitelist of the symbols DECLARED in the analyzed sources, never a path-prefix test. What the checker resolves outside the roots is still external: a type declared under
node_modules/<pkg>is a stub in the module named by the package (the nearestpackage.jsonname, never the resolved.d.tspath, so keys do not depend on what is installed); a type from the compiler’s ownlib.*.d.tsis a stub in the reserved module<lib>(Array,Promise); a name that binds to nothing is a stub in the reserved module<unresolved>, named as written — never a guess from the file’s imports. A module specifier that resolves to nothing becomes a stub module keyed by the specifier as written, so the import edge survives and import fan-out stays honest. Node built-ins are normalised to thenode:form. - A bare specifier that names a package declared UNDER the roots (a monorepo’s own
@acme/pricing) resolves bypackage.jsonname to that package’s source entry when standard resolution fails — with nothing installed and nothing built — because a workspace’s packages are corpus, not dependencies. Every such resolution is counted separately on stderr; a subpath below such a package resolves the same way below its source root, else stubs. anyerases resolution completely: a call or member access through anany-typed (orunknown, or error-typed, or index-signature) receiver has no target. Such edges are dropped and COUNTED in the stderr summary — never emitted with a guessedcandidateslist, since candidate generation is the analyzer’s, which alone has whole-corpus implementor knowledge. The proportion ofany-typed receivers is the honest ceiling on this profile’s resolution rate.- A nameless invocable (arrow function, function expression) is identified by
#line:columnof its first token below the nearest NAMED ancestor’s symbol, and below that ancestor’s own disambiguator when it has one — a column is a source fact, and two can start on one line. At module top level the symbol is empty and the disambiguator alone identifies it (ts:src%2Fa.ts#3:15).const f = () => {}yields two entities: thevariablef and its childarrowFunction; call sites resolve through the variable to the arrow, andinvocationedges target thearrowFunction. A class expression with no binding is not an entity (dropped and counted). - Overloads are ONE entity: TypeScript has no overloading by parameter type at the declaration level, so a function’s overload signatures and its implementation share one key with no parameter-list component; the entity is anchored at the implementation (or the sole ambient signature). Members TypeScript lets coexist under one name carry a disambiguator:
#staticfor a static member beside an instance one,#get/#setfor an accessor pair; a member without a twin renders with no suffix. - Ambient declarations (
.d.ts,declare module,declare global) under the roots describe entities with no implementation in the corpus. They are emitted as real entities anchored in the.d.ts; the implementation they describe, when outside the corpus, is a stub. declaredTypeis populated from the checker’s type where it names ONE declaration — annotated or inferred alike; a union, intersection, literal, primitive, type-parameter or anonymous type leaves it absent rather than guessed, which is whyTTypedEntity.declaredTypeis optional even where the trait is required.- Generic type parameters are not reified as entities; a use of
Array<Order>yields areferenceedge toOrderand one to<lib>/Array, and none toArray’s parameter slot. A type parameter’s constraint and default arereferenceedges from the declaring entity. - Decorators are
annotationUseedges carrying their arguments as written values, under both the legacy (experimentalDecorators) and the TC39 syntax; the decorator itself is an ordinaryfunctionorvariableentity.emitDecoratorMetadatasynthesizes nothing the model shows. - A JSX element whose tag names a component (
<OrderTable/>) is aninvocationof that component — an element is a call by the language definition, so the edge isdeclared; an intrinsic tag (<div>) yields nothing. - A
throwsedge is emitted per writtenthrowstatement whose static type names a declaration, anchored at the throw site; a rethrow targets the caught binding’s static type; a thrown string oranyis dropped and counted. - Measures (TMetrics, METAMODEL.md §3.8):
slocon every type and invocable — lines of its own span that are neither blank nor comment-only, counted with the compiler’s own scanner, so template literals, regular expressions and JSX text cannot swallow a comment marker.cyclomaticon invocables only: 1 + if / for / for-in / for-of / while / do / non-default case / catch / ternary /&&/||/??. A nested arrow or function does NOT contribute to its enclosing invocable: it is its own invocable and carries its own count. - Path resolution depends on
tsconfigpaths,baseUrl,rootDirsandpackage.jsonfields; the extractor reads atsconfig.jsonfor those RESOLUTION options only — never forfiles/include(the roots define the corpus) and never for projectreferences— and creates one program over every source file under the roots. Nothing is built andnode_modulesis never required. - Dynamic
import(expr)andrequire(expr)with a computed, template-literal or variable path are unresolvable: noimportedge is emitted, and the site is counted. Only statically literal specifiers produce edges. CommonJSmodule.exportsreassignment shapes beyond a literalrequire, monkey patching (Obj.prototype.m = fn,Object.assign),thisrebinding throughcall/apply/bind, and computed member accesso[k]with a non-literal key follow the JavaScript profile’s rules: no entity is invented and the site yields at mostaccess/referenceedges at the patch site, or nothing. - Resolution rate and the any-receiver ceiling, measured (M13c audit, every corpus extracted WITHOUT node_modules): 97.4 % on microsoft/TypeScript 4.9.5
src/compiler(69 files, 52 341 entities, 2 684 any-typed receivers, 3 unresolved names — node types with no@types/node); 88.6 % on nestjs/nestpackages(902 files, 29 992 entities, 20 745 any-typed receivers, 78 unresolved names, all from uninstalled fastify/express/class-transformer); 94.4 % on excalidraw (628 files, 43 706 entities, 24 954 any-typed receivers, 41 unresolved names from uninstalled react/mermaid/vitest, 303 stub modules mostly font and asset imports); 96.5 % on codegraph itself (24 887 entities, zero unresolved, 168 imports resolved to workspace packages by name). The rate is a property of the corpus’s DEPENDENCY SURFACE and of whether its dependencies are installed: with no install, every call into a package is a call throughanyand is dropped and counted — the honest lower bound. Install the dependencies (or point--srcat them) to fold those calls to their packages’ stubs. - Members of an object literal are entities only when the literal is BOUND to a name (
const Ops = { … },static x = { … }): array elements, call arguments and return values are unbound, so their parts are not entities and a method written in one is keyed positionally like an arrow. A class expression bound to a variable or a property IS that binding (one entity, the class’s); a class expression bound to nothing is no entity (dropped and counted), nor is anything written inside it. Two declarations of one name in sibling blocks of one scope (if (a) { class X {} } else { class X {} }) are re-keyed by position and named on stderr. - An edge whose endpoint no entity declares is dropped and counted as
unclosableon stderr, never written dangling (schemas/README.md §5): zero on every audited corpus, and a non-zero count names an id-scheme gap. - Getters and setters are
methodentities; a property access that runs an accessor is anaccessedge, not an invocation, so a read that runs code is visible as access — a write lands on the setter, a read on the getter. Class fields and object-literal members share thepropertykind; a parameter property (constructor(readonly x: T)) is both aparameterand aproperty, and an access to it lands on the property. - Locals are entities keyed
#local:name:line:columnbelow their invocable (twolet xin sibling blocks are two entities) and listed in itslocalVariables; a function declared inside an invocable is#fn:name, a type declared there#type:name. Locals and parameters are never edge targets: an access to a local is not a dependency, and a call through a parameter of function type names no declaration (dropped and counted as an indirect call). A member of an anonymous type literal ({ order: Order }) is not an entity — the literal is none. - A written value (TWithValue) is emitted for a
constvariable, areadonlyproperty, an enum member and a parameter default whose initializer is CONSTANT-SHAPED — a literal,-N, an enum member, a type used as a value, arrays and operator expressions over those. TypeScript folds nothing at the declaration, so4 * 25ridesunevaluatedwith its source text where Java would fold it; an enum member’s value is the checker’s computed constant. An initializer that is code — a call, anew, a function, an object literal — carries no value at all: absence is a claim. Such an initializer’s calls and accesses are edges FROM the variable or property (the narrowest declared owner), which then carries TWithInvocations/TWithAccesses. - The metrics pass (
codegraph-metrics, PLAN.md §18): namespaces, enums, type aliases, interface members without a body and ambient declarations have no big-code-analysis space and carry none of the pass’s keys. An exported class’s decorators precede tree-sitter’s class node; the pass joins across the decorator lines. In a.tsxfile big-code-analysis’sploccan count a line or two fewer than this extractor’ssloc(a JSX text token counts every line it spans here), so the join’s witness differs there by that much.