Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Language Support

Refactor DSL provides multi-language support through tree-sitter parsers, enabling AST-based matching and transforms across different programming languages.

Built-in Languages

LanguageExtensionsTree-sitter GrammarLSP Server
Rust.rstree-sitter-rustrust-analyzer
TypeScript.ts, .tsx, .js, .jsxtree-sitter-typescripttypescript-language-server
Python.py, .pyitree-sitter-pythonpyright
Go.gotree-sitter-gogopls
Java.javatree-sitter-javajdtls
C#.cstree-sitter-c-sharpomnisharp
Ruby.rb, .rake, .gemspectree-sitter-rubysolargraph
C/C++.c, .h, .cpp, .hpptree-sitter-cppclangd

Language Registry

The LanguageRegistry manages available languages:

#![allow(unused)]
fn main() {
use refactor::prelude::*;

let registry = LanguageRegistry::new();

// Find by extension
let rust = registry.by_extension("rs");

// Find by name
let python = registry.by_name("python");

// Detect from file path
let lang = registry.detect(Path::new("src/main.rs"));

// List all languages
for lang in registry.all() {
println ! ("{}: {:?}", lang.name(), lang.extensions());
}
}

Using Languages Directly

Each language implements the Language trait:

use refactor::lang::{Rust, TypeScript, Python};

// Parse source code
let tree = Rust.parse("fn main() {}") ?;

// Create a query
let query = Rust.query("(function_item name: (identifier) @fn)") ?;

// Check extension
assert!(Rust.matches_extension("rs"));
assert!(TypeScript.matches_extension("tsx"));

Language Trait

#![allow(unused)]
fn main() {
pub trait Language: Send + Sync {
    /// Language name (lowercase)
    fn name(&self) -> &'static str;

    /// File extensions handled
    fn extensions(&self) -> &[&'static str];

    /// Tree-sitter grammar
    fn grammar(&self) -> tree_sitter::Language;

    /// Parse source into AST
    fn parse(&self, source: &str) -> Result<Tree>;

    /// Create a tree-sitter query
    fn query(&self, pattern: &str) -> Result<Query>;

    /// Check if extension matches
    fn matches_extension(&self, ext: &str) -> bool;
}
}

Language-Specific Examples

Rust

#![allow(unused)]
fn main() {
use refactor::lang::Rust;

let source = r#"
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}
"#;

// Parse
let tree = Rust.parse(source) ?;
assert!(!tree.root_node().has_error());

// Query for functions
let matcher = AstMatcher::new()
.query("(function_item name: (identifier) @fn)");
let matches = matcher.find_matches(source, & Rust) ?;
}

Common Rust queries:

#![allow(unused)]
fn main() {
// Functions
"(function_item name: (identifier) @fn)"

// Structs
"(struct_item name: (type_identifier) @struct)"

// Enums
"(enum_item name: (type_identifier) @enum)"

// Impl blocks
"(impl_item type: (type_identifier) @impl)"

// Use statements
"(use_declaration) @use"

// Macro invocations
"(macro_invocation macro: (identifier) @macro)"
}

TypeScript

#![allow(unused)]
fn main() {
use refactor::lang::TypeScript;

let source = r#"
function greet(name: string): string {
    return `Hello, ${name}!`;
}

const arrow = (x: number) => x * 2;
"#;

let tree = TypeScript.parse(source) ?;

let matcher = AstMatcher::new()
.query("(function_declaration name: (identifier) @fn)");
let matches = matcher.find_matches(source, & TypeScript) ?;
}

Common TypeScript queries:

#![allow(unused)]
fn main() {
// Functions
"(function_declaration name: (identifier) @fn)"

// Arrow functions
"(arrow_function) @arrow"

// Classes
"(class_declaration name: (type_identifier) @class)"

// Interfaces
"(interface_declaration name: (type_identifier) @interface)"

// Type aliases
"(type_alias_declaration name: (type_identifier) @type)"

// Imports
"(import_statement) @import"
}

Python

#![allow(unused)]
fn main() {
use refactor::lang::Python;

let source = r#"
def greet(name: str) -> str:
    return f"Hello, {name}!"

class Greeter:
    def say_hello(self):
        print("Hello!")
"#;

let tree = Python.parse(source) ?;

let matcher = AstMatcher::new()
.query("(function_definition name: (identifier) @fn)");
let matches = matcher.find_matches(source, & Python) ?;
}

Common Python queries:

#![allow(unused)]
fn main() {
// Functions
"(function_definition name: (identifier) @fn)"

// Classes
"(class_definition name: (identifier) @class)"

// Methods (inside class)
"(class_definition body: (block (function_definition name: (identifier) @method)))"

// Imports
"(import_statement) @import"
"(import_from_statement) @from_import"

// Decorators
"(decorated_definition decorator: (decorator) @decorator)"
}

Go

#![allow(unused)]
fn main() {
use refactor::lang::Go;

let source = r#"
package main

import "fmt"

func greet(name string) string {
    return fmt.Sprintf("Hello, %s!", name)
}

type Greeter struct {
    Name string
}

func (g *Greeter) SayHello() {
    fmt.Printf("Hello from %s\n", g.Name)
}
"#;

let tree = Go.parse(source) ?;

let matcher = AstMatcher::new()
.query("(function_declaration name: (identifier) @fn)");
let matches = matcher.find_matches(source, & Go) ?;
}

Common Go queries:

#![allow(unused)]
fn main() {
// Functions
"(function_declaration name: (identifier) @fn)"

// Methods (with receiver)
"(method_declaration
  receiver: (parameter_list) @receiver
  name: (field_identifier) @method)"

// Structs
"(type_declaration (type_spec name: (type_identifier) @struct type: (struct_type)))"

// Interfaces
"(type_declaration (type_spec name: (type_identifier) @interface type: (interface_type)))"

// Imports
"(import_declaration) @import"
"(import_spec path: (interpreted_string_literal) @path)"

// Package declaration
"(package_clause (package_identifier) @package)"
}

Java

#![allow(unused)]
fn main() {
use refactor::lang::Java;

let source = r#"
package com.example;

import java.util.List;

public class Greeter {
    private String name;

    public Greeter(String name) {
        this.name = name;
    }

    public String greet() {
        return "Hello, " + name + "!";
    }
}
"#;

let tree = Java.parse(source) ?;

let matcher = AstMatcher::new()
.query("(class_declaration name: (identifier) @class)");
let matches = matcher.find_matches(source, & Java) ?;
}

Common Java queries:

#![allow(unused)]
fn main() {
// Classes
"(class_declaration name: (identifier) @class)"

// Interfaces
"(interface_declaration name: (identifier) @interface)"

// Methods
"(method_declaration name: (identifier) @method)"

// Constructors
"(constructor_declaration name: (identifier) @constructor)"

// Fields
"(field_declaration declarator: (variable_declarator name: (identifier) @field))"

// Imports
"(import_declaration) @import"

// Annotations
"(annotation name: (identifier) @annotation)"

// Package declaration
"(package_declaration) @package"
}

C#

#![allow(unused)]
fn main() {
use refactor::lang::CSharp;

let source = r#"
using System;

namespace Example
{
    public class Greeter
    {
        public string Name { get; set; }

        public string Greet()
        {
            return $"Hello, {Name}!";
        }
    }
}
"#;

let tree = CSharp.parse(source) ?;

let matcher = AstMatcher::new()
.query("(class_declaration name: (identifier) @class)");
let matches = matcher.find_matches(source, & CSharp) ?;
}

Common C# queries:

#![allow(unused)]
fn main() {
// Classes
"(class_declaration name: (identifier) @class)"

// Interfaces
"(interface_declaration name: (identifier) @interface)"

// Methods
"(method_declaration name: (identifier) @method)"

// Properties
"(property_declaration name: (identifier) @property)"

// Fields
"(field_declaration (variable_declaration (variable_declarator (identifier) @field)))"

// Using directives
"(using_directive) @using"

// Namespaces
"(namespace_declaration name: (_) @namespace)"

// Records (C# 9+)
"(record_declaration name: (identifier) @record)"
}

Ruby

#![allow(unused)]
fn main() {
use refactor::lang::Ruby;

let source = r#"
class Greeter
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def greet
    "Hello, #{@name}!"
  end
end

module Helpers
  def self.format_name(name)
    name.capitalize
  end
end
"#;

let tree = Ruby.parse(source) ?;

let matcher = AstMatcher::new()
.query("(class name: (constant) @class)");
let matches = matcher.find_matches(source, & Ruby) ?;
}

Common Ruby queries:

#![allow(unused)]
fn main() {
// Classes
"(class name: (constant) @class)"

// Modules
"(module name: (constant) @module)"

// Methods
"(method name: (identifier) @method)"

// Singleton methods (class methods)
"(singleton_method name: (identifier) @class_method)"

// Instance variables
"(instance_variable) @ivar"

// Requires
"(call method: (identifier) @method (#eq? @method \"require\"))"

// attr_accessor, attr_reader, attr_writer
"(call method: (identifier) @accessor (#match? @accessor \"^attr_\"))"

// Blocks
"(block) @block"
"(do_block) @do_block"
}

Adding Custom Languages

To add support for a new language:

  1. Add the tree-sitter grammar dependency to Cargo.toml
  2. Implement the Language trait
  3. Register with LanguageRegistry
#![allow(unused)]
fn main() {
use refactor::lang::Language;
use tree_sitter::Language as TsLanguage;

// Add to Cargo.toml:
// tree-sitter-go = "0.20"

pub struct Go;

impl Language for Go {
    fn name(&self) -> &'static str {
        "go"
    }

    fn extensions(&self) -> &[&'static str] {
        &["go"]
    }

    fn grammar(&self) -> TsLanguage {
        tree_sitter_go::LANGUAGE.into()
    }
}

// Register
let mut registry = LanguageRegistry::new();
registry.register(Box::new(Go));
}

Language Detection

Automatic language detection from file paths:

#![allow(unused)]
fn main() {
let registry = LanguageRegistry::new();

// Detects by extension
let lang = registry.detect(Path::new("src/main.rs"));
assert_eq!(lang.unwrap().name(), "rust");

let lang = registry.detect(Path::new("app/index.tsx"));
assert_eq!(lang.unwrap().name(), "typescript");

// No extension or unknown
let lang = registry.detect(Path::new("Makefile"));
assert!(lang.is_none());
}

Integration with Refactor

Language detection happens automatically in the refactor pipeline:

#![allow(unused)]
fn main() {
Refactor::in_repo("./project")
.matching( | m| m
.files( | f| f.extension("rs"))  // Language inferred from extension
.ast( | a| a.query("(function_item @fn)")))  // Rust query
.transform(/* ... */)
.apply() ?;
}

For explicit language specification:

#![allow(unused)]
fn main() {
let matcher = AstMatcher::new()
.query("(function_item @fn)");

// Explicit language
let matches = matcher.find_matches(source, & Rust) ?;

// Or use registry
let registry = LanguageRegistry::new();
let lang = registry.by_name("rust").unwrap();
let matches = matcher.find_matches(source, lang) ?;
}