GitHub

Supported Languages

RCE Code fully parses seven languages with tree-sitter — Python, JavaScript, TypeScript, Go, Rust, Java, and Ruby. For these, it extracts symbols, a call graph, and definition-aligned chunks. Every other file type is still indexed for lexical and grep search; it just doesn’t get structural data.

A language is “fully supported” when three pieces of data exist for it: a tree-sitter grammar registered in the parser pool, a small LanguageProfile (namespace separator + comment node types), and two query files (<lang>.scm for definitions, <lang>.refs.scm for references). There is no per-language engine code — everything is declarative. See Adding a Language for the full anatomy.

The language matrix

Extensions come from the detector in ingest/language.py; the separator and comment handling come from the _PROFILES registry in languages.py.

LanguageFile extensionsQualified-name separatorNotes
Python.py .pyi.Functions, classes, methods; calls and import / from … import.
JavaScript.js .jsx .mjs .cjs.Function/class declarations + methods. Arrow-function consts are not captured as defs.
TypeScript.ts .tsx.Adds interfaces, enums, and type aliases. Same arrow-function caveat as JS.
Go.go.Funcs, receiver methods, struct/interface types. Receiver type is omitted from the qualified name.
Rust.rs::Fns, structs, traits, enums; macro invocations count as calls. impl fns recorded as functions.
Java.java.Classes, methods, interfaces, enums; method invocations and import declarations.
Ruby.rb::Methods, singleton methods, classes/modules. Imports = require / require_relative only.

Detection is more than extensions

detect_language resolves a file in order: exact filename (Dockerfile, Makefile), then extension, then a #! shebang. So an extensionless script starting with #!/usr/bin/env ruby is detected as Ruby even without a .rb suffix.

What RCE Code extracts everywhere

For all seven languages the pipeline produces the same three kinds of structural data, driven entirely by the two query files plus the profile:

  • Definitions → symbols. The <lang>.scm query captures each definition node as @def.<kind> with its name as @name. The extractor walks each definition’s ancestors to build a qualified name joined with the profile’s separator (. or ::), and a @def.function nested inside a class is auto-promoted to a METHOD.
  • Calls + imports → references. The <lang>.refs.scm query captures callees as @ref.call and imported names as @ref.import. Each reference is linked to the innermost enclosing symbol, forming the edges of the call graph and repo map.
  • Definition-aligned chunks. The AST chunker cuts at top-level definition boundaries (never mid-body) and attaches leading comments — using each language’s comment node types (comment for most; line_comment / block_comment for Rust and Java).

Honest limitations

RCE Code favors a small, declarative query surface over per-language special cases, so a few constructs are deliberately not modeled yet. These are documented bounds, not bugs:

  • Go receiver methods omit the receiver. func (a GoAnimal) Speak() is correctly classified as a method, but the receiver type lives outside the method’s AST subtree, so the qualified name is Speak, not GoAnimal.Speak.
  • Rust impl functions aren’t methods. Associated functions live in impl blocks, syntactically detached from the struct, so they’re recorded as function with no Type:: qualifier.
  • JS/TS arrow-function consts aren’t captured. const f = () => {} and function expressions are skipped — only function_declaration and method_definition become defs.
  • Aliased imports aren’t captured. import numpy as np or from m import User as U nest the original name under an aliased_import node the queries don’t match, so that import edge is lost.
  • Ruby imports are require only. Ruby has no dedicated import node, so only require and require_relative are treated as imports (via an anchored predicate); other Ruby loading idioms aren’t.
  • Calls and imports only. Non-call member access and type-annotation references are not captured for any language.

Everything else is still indexed

A file whose language has no registered grammar/profile — C, C++, C#, PHP, shell, SQL, or any unrecognized type — is not skipped. The pipeline falls back to a fixed-window line chunker, so the content is still chunked, hashed, embedded, and searchable lexically and semantically; live grep works over it too. What you lose is the structural layer: no symbols, no references, no call graph, no definition-aligned chunks.

ingest/pipeline.py — the fork
profile = get_profile(language)
if profile is not None and _POOL.supports(language):
# full path: parse -> symbols -> AST chunks -> references
tree = _POOL.parse(language, data)
symbols = extract_symbols_from_tree(_POOL, tree, data, language)
...
else:
# fallback: line-window chunks only, still lexically searchable
parsed = chunk_text(text, language, max_lines=config.max_chunk_lines)

Even a clean grammar can fall back

tree-sitter is error-tolerant, but if a supported file parses with more than a handful of ERROR / MISSING nodes, RCE Code marks it FALLBACK_LINE and uses the line chunker for that file rather than emitting a broken AST. Symbols already extracted are kept; references are skipped.

Want another language?

Because every language-specific detail is data — a grammar wheel, a profile, and two query files — adding one is a data-driven change with no engine edits. Ruby was added this way as RCE Code’s seventh language and is the worked example in the guide.