Skip to main content

Language Support

ctx uses tree-sitter for parsing, providing accurate symbol extraction across multiple programming languages. This page details what's extracted from each language and how relationships are tracked.

Supported Languages Overview

LanguageExtensionsSymbol ExtractionEdge TrackingStatus
Rust.rsFullCalls, Implements, ImportsFull
TypeScript.tsFullCalls, Extends, Implements, ImportsFull
TSX.tsxFullCalls, Extends, Implements, ImportsFull
JavaScript.js, .mjs, .cjsFullCalls, Extends, ImportsFull
JSX.jsxFullCalls, Extends, ImportsFull
Python.py, .pyiFullCalls, Extends, ImportsFull
Go.goFullCalls, ImportsFull
Solidity.solFullCallsFull
YAML.yaml, .ymlFile tracking onlyN/APartial

Rust

Extracted Symbols

KindExampleNotes
Functionfn main()Top-level functions
Methodfn new(&self)Functions in impl blocks
Structstruct UserStruct definitions
Enumenum StatusEnum definitions
Traittrait DisplayTrait definitions
Type aliastype Result<T> = ...Type aliases
Constconst MAX: i32Constants
Staticstatic GLOBAL: &strStatic variables

Visibility Detection

  • pub -> public
  • pub(crate) -> crate
  • pub(super) -> super
  • No modifier -> private

Example

/// A user in the system.
pub struct User {
    pub id: u64,
    pub name: String,
}

impl User {
    /// Create a new user.
    pub fn new(name: &str) -> Self {
        Self { 
            id: generate_id(),
            name: name.to_string() 
        }
    }
    
    /// Validate the user.
    fn validate(&self) -> bool {
        !self.name.is_empty()
    }
}

trait Authenticate {
    fn verify(&self) -> bool;
}

impl Authenticate for User {
    fn verify(&self) -> bool {
        self.validate()
    }
}

Extracted symbols:

  • User (struct, public)
  • User::new (method, public)
  • User::validate (method, private)
  • Authenticate (trait)
  • User::verify (method, public via trait)

Extracted edges:

  • User::new calls generate_id
  • User::new calls to_string
  • User::verify calls validate
  • User implements Authenticate

Documentation Extraction

Rust doc comments (/// and //!) are extracted:

  • First line becomes the brief field
  • Full content becomes docstring

TypeScript

Extracted Symbols

KindExampleNotes
Functionfunction foo()Named functions
Arrow Functionconst foo = () => {}With const declaration
Classclass UserClass declarations
Methodauthenticate()Class methods
Interfaceinterface IUserInterface declarations
Type Aliastype UserId = stringType definitions
Enumenum StatusEnum declarations

Visibility Detection

  • export -> public
  • No export -> private
  • private keyword in class -> private
  • public keyword in class -> public
  • protected keyword in class -> protected

Example

/** User service for authentication. */
export interface UserService {
  authenticate(token: string): Promise<User>;
}

/** Default implementation. */
export class DefaultUserService implements UserService {
  constructor(private db: Database) {}
  
  async authenticate(token: string): Promise<User> {
    const decoded = decodeToken(token);
    return this.db.findUser(decoded.userId);
  }
  
  private validateToken(token: string): boolean {
    return token.length > 0;
  }
}

/** Decode a JWT token. */
export const decodeToken = (token: string): TokenPayload => {
  return jwt.decode(token);
};

type UserId = string;

Extracted symbols:

  • UserService (interface, public)
  • DefaultUserService (class, public)
  • DefaultUserService.authenticate (method, public)
  • DefaultUserService.validateToken (method, private)
  • decodeToken (function, public)
  • UserId (type, private)

Extracted edges:

  • DefaultUserService implements UserService
  • authenticate calls decodeToken
  • authenticate calls findUser
  • decodeToken calls jwt.decode

JSDoc Extraction

JSDoc comments (/** */) are extracted:

  • @description or first line -> brief
  • Full content -> docstring

JavaScript / JSX

Same as TypeScript, minus type-specific constructs (interfaces, type aliases, enums).

Additional JSX Support

// Function component
export function Button({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>;
}

// Arrow function component
export const Card = ({ title, children }) => (
  <div className="card">
    <h2>{title}</h2>
    {children}
  </div>
);

Extracted:

  • Button (function, public)
  • Card (function, public)

TSX

Combines TypeScript and JSX support:

interface ButtonProps {
  label: string;
  onClick: () => void;
}

export const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
  return <button onClick={onClick}>{label}</button>;
};

Extracted:

  • ButtonProps (interface, private)
  • Button (function, public)

Python

Extracted Symbols

KindExampleNotes
Functiondef foo():Top-level functions
Async Functionasync def foo():Async functions
Classclass User:Class definitions
Methoddef validate(self):Instance methods
Static Method@staticmethod def create():Static methods
Class Method@classmethod def from_dict(cls):Class methods
ConstantMAX_RETRIES = 3UPPER_CASE names at module level

Visibility Detection

  • Names starting with _ -> private
  • Names starting with __ (not __dunder__) -> private (name mangling)
  • All other names -> public

Example

"""User module for authentication."""

MAX_RETRIES = 3

class User:
    """A user in the system."""
    
    def __init__(self, name: str):
        """Initialize the user."""
        self.name = name
    
    def validate(self) -> bool:
        """Validate the user."""
        return len(self.name) > 0
    
    @staticmethod
    def from_dict(data: dict) -> "User":
        """Create a user from a dictionary."""
        return User(data["name"])
    
    def _internal_check(self):
        """Private method."""
        pass

class Admin(User):
    """Admin user with elevated privileges."""
    
    def __init__(self, name: str, permissions: list):
        super().__init__(name)
        self.permissions = permissions

async def fetch_user(user_id: int) -> User:
    """Fetch a user from the database."""
    data = await db.get(user_id)
    return User.from_dict(data)

Extracted symbols:

  • MAX_RETRIES (constant, public)
  • User (class, public)
  • User.__init__ (method, private - starts with _)
  • User.validate (method, public)
  • User.from_dict (method, public)
  • User._internal_check (method, private)
  • Admin (class, public)
  • Admin.__init__ (method, private)
  • fetch_user (function, public)

Extracted edges:

  • Admin extends User
  • Admin.__init__ calls super().__init__
  • User.from_dict calls User
  • fetch_user calls db.get
  • fetch_user calls User.from_dict

Docstring Extraction

Python docstrings (triple-quoted strings) are extracted:

  • First line -> brief
  • Full content -> docstring

Go

Extracted Symbols

KindExampleNotes
Functionfunc Handle()Top-level functions
Methodfunc (s *Server) Start()Methods with receivers
Structtype User struct {}Struct type definitions
Interfacetype Reader interface {}Interface type definitions
Constconst MaxRetries = 3Constants

Visibility Detection

  • Exported identifiers (capitalized first letter) -> public
  • Unexported identifiers (lowercase first letter) -> private

Example

package auth

// User represents an account in the system.
type User struct {
    ID   uint64
    Name string
}

// Authenticator verifies credentials.
type Authenticator interface {
    Verify(token string) (*User, error)
}

// NewUser creates a user with a generated ID.
func NewUser(name string) *User {
    return &User{ID: generateID(), Name: name}
}

func (u *User) validate() bool {
    return len(u.Name) > 0
}

Extracted symbols:

  • User (struct, public)
  • Authenticator (interface, public)
  • NewUser (function, public)
  • User.validate (method, private)

Extracted edges:

  • NewUser calls generateID

Documentation Extraction

Go doc comments (// immediately preceding a declaration) are extracted:

  • First line becomes the brief field
  • Full comment becomes docstring

Solidity

Extracted Symbols

KindExampleNotes
Contractcontract TokenContract definitions
Functionfunction transfer()Contract functions
Modifiermodifier onlyOwnerFunction modifiers
Eventevent TransferEvent definitions
Structstruct ProposalStruct definitions
Enumenum StatusEnum definitions
Errorerror UnauthorizedCustom errors

Visibility Detection

  • public -> public
  • external -> public
  • internal -> crate (treated as internal)
  • private -> private

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Token contract
/// @notice Implements ERC20-like functionality
contract Token {
    mapping(address => uint256) public balances;
    
    event Transfer(address indexed from, address indexed to, uint256 amount);
    
    error InsufficientBalance(uint256 available, uint256 required);
    
    modifier onlyPositive(uint256 amount) {
        require(amount > 0, "Amount must be positive");
        _;
    }
    
    /// @notice Transfer tokens to another address
    /// @param to Recipient address
    /// @param amount Amount to transfer
    function transfer(address to, uint256 amount) 
        external 
        onlyPositive(amount) 
    {
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance(balances[msg.sender], amount);
        }
        balances[msg.sender] -= amount;
        balances[to] += amount;
        emit Transfer(msg.sender, to, amount);
    }
}

Extracted symbols:

  • Token (contract, public)
  • Token.transfer (function, external)
  • Token.onlyPositive (modifier)
  • Transfer (event)
  • InsufficientBalance (error)

NatSpec Extraction

NatSpec comments (/// and /** */) are extracted:

  • @title or @notice -> brief
  • Full content -> docstring

YAML

YAML files are tracked but not parsed for symbols (YAML doesn't have functions/classes).

What's indexed:

  • File path
  • File hash (for change detection)
  • Language type

Use cases:

  • Include config files in ctx query files
  • Track changes to CI/CD configs
  • Include in context generation for reference

Edge Types Summary

Edge TypeLanguagesDescription
callsAllFunction/method calls
extendsTS, JS, PythonClass inheritance
implementsTS, RustInterface/trait implementation
importsAll (except Solidity)Module imports

Limitations

Cross-File Resolution

Call targets are resolved by name within the same codebase. External calls (to libraries) show as unresolved:

ctx query deps myFunction
Dependencies of 'myFunction':
------------------------------------------------------------
  calls internalHelper (line 12)      # Resolved
  calls externalLib (line 15)         # Unresolved (external)

Dynamic Calls

Dynamic or computed calls cannot be tracked:

// Static call - tracked
processData(input);

// Dynamic call - not tracked
const fn = getHandler(type);
fn(input);

// Not tracked
obj[methodName]();

Macros (Rust)

Macro-generated code is not analyzed:

// The generated impl is not indexed
#[derive(Debug, Clone)]
struct MyStruct { ... }

// Macro invocations are not followed
println!("Hello");  // Not tracked as a call

Decorators (Python)

Decorated functions are tracked, but decorator calls are not:

@cache  # Not tracked as a call
def expensive_operation():
    pass

Type Inference

We don't perform type inference, so method calls on inferred types may not resolve:

// Resolved (explicit type)
const user: User = getUser();
user.validate();  // Knows validate is on User

// May not resolve (inferred type)
const user = getUser();
user.validate();  // We don't know user is User

Indirect Calls

Calls through variables, callbacks, or higher-order functions are not tracked:

// Not tracked
const callback = processData;
callback(input);

// Not tracked
[1, 2, 3].map(transform);

Adding Language Support

To add support for a new language:

  1. Add the tree-sitter crate to Cargo.toml:

    tree-sitter-newlang = "0.x"
  2. Create a parser module in src/parser/:

    // src/parser/newlang.rs
    pub struct NewLangParser { ... }
    impl NewLangParser {
        pub fn parse(&mut self, file_path: &str, source: &str) -> Option<ParseResult>;
    }
  3. Define tree-sitter queries for symbol extraction

  4. Add the language to the Language enum in src/parser/mod.rs

  5. Update is_supported() in CodeParser

See src/parser/rust.rs or src/parser/typescript.rs for examples.

Language Detection

ctx detects language by file extension:

match extension {
    "rs" => Rust,
    "ts" => TypeScript,
    "tsx" => Tsx,
    "js" | "mjs" | "cjs" => JavaScript,
    "jsx" => Jsx,
    "py" | "pyi" => Python,
    "sol" => Solidity,
    "yaml" | "yml" => Yaml,
    "go" => Go,
    _ => Unknown,
}

Unknown extensions are skipped during indexing but included in context generation.