5.21 source-tree — a project's source in any of fourteen languages
A source-tree producer reads a project’s source files in TypeScript (and TSX), JavaScript (and JSX),
Python, Go, Rust, C, C++, C#, Ruby, PHP, Kotlin, Scala, Swift and Java, parsing each with that
language’s grammar when a query reaches it. Nothing is ingested. Java has emits of its own besides,
which name types by binary name and say what the file alone shows about types (below), and the kind
also reads the source a jar was compiled from (from: jar), with the rows of §5.19.
- { name: pyDeclarations, kind: source-tree, emit: declarations, languages: [python] }- { name: allFiles, kind: source-tree, emit: files } # every language- { name: goCalls, kind: source-tree, emit: calls, languages: [go], maxFileKb: 256 }- { name: javaMetrics, kind: source-tree, emit: captures, languages: [java], query: queries/java-metrics.scm }// Which handler serves a route, whatever the language: decorators, annotations and attributes alikeMATCH (p:CodeProject {key: 'https://github.com/acme/shop'})-[:DECLARES]->(d:SourceDeclaration)WHERE any(a IN d.annotationText WHERE a CONTAINS '/items/{id}')RETURN d.language, d.owner, d.name, d.annotationText, d.path, d.fromLineThe contract:
-
The key is a project’s location: an https git URL, optionally
@ref(a commit, tag or branch; the default branch without one), read at the commit it resolves to; or a folder under the world’sdata/tree, an operator-trusted root, or the folder the operator mounted for project keys. A git project is read once per commit and kept; a folder is read afresh for each fetch. A tag or branch, the default branch included, is resolved to its commit at most once a minute, so a pushed commit is read by the first fetch a minute after it. -
Five emits, each row in the columns of a standard-model parent (and a sixth,
captures, and a seventh,version, below), so a realm’s language children of those parents (PythonDeclarationunderSourceDeclaration) carry them under the same names as Java’s:files(Source):root,path,language,lines,sha256,commit(git),url(on a host with a file view),parsed, anderrors— the number of syntax errors the file’s tree holds. Error recovery keeps everything around a broken region, so a file with errors still answers.declarations(SourceDeclaration): each class, interface, struct, enum, trait, module, namespace,impl, function, method, constructor, field, variable and constant the language has:kind,name,owner(the enclosing types, outermost first,.-joined; a Go receiver or C++’sOrder::totalnames its type explicitly),fromLine/toLine,signature(up to the body, on one line, without annotations),javadoc(the doc comment above it, or a docstring), the numberedtextof a non-type declaration (at most 400 lines),annotations(the names of its decorators, annotations or attributes) andannotationText(each in full).imports(SourceImport):imported(what it imports from: a module, package, path or header),names(what it brings in, in source order; empty for a whole module),alias,isStatic(false).calls(CallSite):method(the called name),receiver(what it is called on, as written),arguments(each as written),enclosing(the function or method it is in — for a call inside a decorator or attribute, the declaration it decorates),receiverTypeandargumentTypes(empty: syntax does not say) andresolution, alwaysname.references(SourceReference): each name used where a type goes — a parameter’s, field’s or return type, a base class or interface, a generic argument, an annotation, a constructor call — asname(the last segment, as written),enclosing(the innermost declaration it is used in: a field’s type is used in the field; empty at the top level) andresolution, alwaysname. A declaration’s own name is not a reference (class Orderdoes not useOrder), nor is a generic parameter inside the declaration that declares it (Tinclass Box<T>). A built-in type (string,int) is a reference like any other; join to the project’s declarations to keep the project’s own. Every row carriesid,sourceId(its file’sid),path,language,line,url,projectandanchorKey.
-
versionsays whether the project changed, without reading it. One row per project key:project(as read),commit(a repository’s, the default branch or@refresolved as above) andversion— the commit, or for a folder a digest of every file’s path, size and modification time. No file is read or parsed, so it is cheap enough to ask every minute; it is what adocumentssource watches (README,sync.watch) to re-read a project only when a commit lands. A folder’sversionmoves when a file is added, removed or rewritten, even with the same bytes. -
idnames a row where it was read, not what it is. It holds the project as read (host/repo@<commit>for a repository) and the row’s line and column, so it changes with every commit and whenever the code above it moves. It is the key to join rows of one read (enclosingId = d.id). To keep anything about a declaration from one read to the next — a document named after it, a stored note — name it by the project key,path,kind,ownerandname, numbering the second and later of one name in a file by line; and leavetext’s line numbers out of anything compared across reads. -
A call or a reference is matched by name.
resolution: namesays the callee or type is only known by its name: any function or type of that name may be the one meant. Present such a row as “calls something namedsave” or “uses a type namedOrder”, never as a resolved edge; where two of the project’s types share a name, say so. Java’s rows sayexact(from bytecode, §5.18) ortype(the receiver’s static type is known, §5.19). -
languagesnarrows what is read to the named languages (empty reads them all). A name no language has is refused with aPRODUCER_ERRORwarning listing the languages there are. -
capturesruns the realm’s own query.query:names a tree-sitter query file (.scm) in the realm, relative to its root, andlanguages:names the one language whose grammar it is written against. Each node the query captures is one row:capture(the capture’s name, without@),nodeType(the grammar’s name for the node,cast_expression),text(the node’s text, whitespace collapsed, at most 200 characters),line/column/toLine/toColumn(where its first and last characters are),match(an id shared by the captures of one match, so a cast and its@cast.typeare read together),pattern(the pattern’s index in the query, from 0), and the innermost declaration it is in:enclosingId(that declaration’sdeclarationsrowid, soWHERE c.enclosingId = d.idjoins them),enclosing(its name) andenclosingKind(empty at the top level). A node two patterns capture under one name is one row. Text predicates (#eq?,#not-eq?,#match?,#any-of?) are applied.; queries/java-metrics.scm(if_statement) @if[(for_statement) (enhanced_for_statement) (while_statement) (do_statement)] @loop(cast_expression type: (_) @cast.type) @cast// Ifs per method, longest first; commented-out code is a comment node, so it never countsMATCH (p:CodeProject {key: $project})-[:CAPTURES]->(c:JavaCapture {capture: 'if'})RETURN c.path, c.enclosing, count(*) AS ifs ORDER BY ifs DESCRefused with a
PRODUCER_ERRORwarning and no rows: a query file the realm does not have, one with a character outside ASCII (tree-sitter would silently lose its end; the warning names the line), and one that does not compile against the grammar (the warning gives tree-sitter’s row and column). Refused when the realm is loaded:query:on another emit,captureswithoutquery:, aquery:that leaves the realm (.., a leading/), andcapturesnaming other than one language. A file capturing more thanmaxCapturesnodes (default 10000, at most 100000) answers its firstmaxCapturesand is reported. Captures are not kept: each fetch parses the files again. -
Each file is bounded. A file larger than
maxFileKb(default 512, at most 4096) is not read; a file that takes longer thanparseTimeoutMs(default 2000, at most 60000) to parse is abandoned; pastmaxFiles(default 20000) source files the rest are not read. Each is aPRODUCER_ERRORwarning naming the file, never a half-read file. A key that names no readable project is a warning with the reason. -
What is not read. Directories a build or package manager writes (
node_modules,target,build,out,.git, …) are skipped. Generated files are read like any other. -
Java’s own emits, by binary name. A name is resolved as the file resolves it, from the file alone: written qualified, a type the file declares (
pkg.Outer$Inner), a single-type import,java.lang, or (with no on-demand import that could supply it) the file’s own package. Where an on-demand import could, the name stays as written and the row says it is unresolved.types: each class, interface, enum, record and annotation type a project declares outside a method or anonymous class —name(binary),simpleName,packageName,kind,extendsandimplements(resolved),annotations(resolved),annotationText,modifiers,isTopLevel,line,lastLine,module(the Maven module whosepom.xmldirectory holds the file;''for the root),link.members: each field (one per variable), constructor and method of those types —owner(binary),name,kind,signature,type(a field’s element type, resolved),returnType,parameters,parameterTypes(resolved),annotations,modifiers, and the declaration columns (fromLine,toLine,text,javadoc,annotationText). Id:<project>!<owner>#<name>(<parameter types>).annotations: each annotation written on a type, member or parameter, with its attributes as written —name,qualifiedName(null where the file cannot say),attributes(name=value),value/values,text,targetKind,targetId,targetName,owner.importsof a Java file carry, beside every language’s columns,name,isStatic,isWildcardandpackageName.configs: every key of the project’s.propertiesand YAML files, flattened to a dotted path (server.ssl.enabled,servers[0].url), withvalueas written,document(of a multi-document YAML file, from 0),fileName,moduleandline. YAML is composed, never constructed.projectFiles: every file of the project, whatever its language —path,fileName,extension,module,link.
-
from: jar: the source a jar was compiled from. The key is what ajarrow (§5.18) carries: asourceKey(<jar path>!com/x/Cache.java) or aspanKey(…#<line>#<name>). The file is looked for beside the jar as a-sources.jar, then underroots:in order, as §5.19 says, with §5.19’s emits, columns and ids:files,spans(context, default 6),declarations,calls(the calls on the key’s line, withreceiverType,argumentTypes,argumentTypesResolvedandresolution: typewhere the receiver’s static type is known) andlocals.maxFileKbdefaults to 1024 for jar keys. A file in another language answersfilesandspans, with a warning for the rest; a file with syntax errors answers around them.- { name: javaSourceSpan, kind: source-tree, from: jar, emit: spans, roots: [src], context: 6 } # key: a spanKey- { name: javaCallSitesAt, kind: source-tree, from: jar, emit: calls, roots: [src] } # key: a spanKey- { name: upstream, kind: source-tree, from: jar, emit: declarations,roots: [ { git: "https://github.com/acme/acme", ref: "v{version}" } ] }An argument’s type is what the file shows:
new X(…), a literal, a cast, a parameter (a method’s, a typed lambda’s, a catch clause’s), a local declared before the call, a field of an enclosing type. A method’s return value, an inherited field, a parenthesized expression and a lambda’s inferred parameter are unknown (""), never guessed. Expressions (arguments,receiver,initializer, a call’stext) are as written, whitespace collapsed.
Cost: the first query of a git project pays for a shallow, blob-less fetch of the commit and one fetch of the language files’ contents; each file is parsed once, the first time an emit needs it, and kept with the commit. A folder is listed and its files parsed again on each fetch. A repository of a few hundred files is read, from a cold git cache, in about ten seconds.

