Skip to main content

add-language-support

Implements mutation testing support for new programming languages using tree-sitter grammars and the LanguageEngine trait. Triggers on "add language support", "add [language] support", "implement [language]", "new language", or mentions of specific languages like Python, TypeScript, C++, Java.

Source facts

Repository
trailofbits/mewt
Last source activity
June 18, 2026 at 20:23
Detected SKILL.md language
English
Stars
61
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
add-language-support
description
Implements mutation testing support for new programming languages using tree-sitter grammars and the LanguageEngine trait. Triggers on "add language support", "add [language] support", "implement [language]", "new language", or mentions of specific languages like Python, TypeScript, C++, Java.
license
Apache-2.0
metadata
{"author":"trailofbits","version":"1.1","mewt-version":">=2.0.0"}
allowed-tools
["Read","Write","Edit","Bash","Glob","AskUserQuestion"]
# Adding Language Support to Mewt Implements mutation testing support for new programming languages using tree-sitter grammars and the LanguageEngine trait. ## When to Use Use this skill when: - Adding support for a new programming language (Python, JavaScript, Go, etc.) - Implementing tree-sitter grammar integration - User asks to "add language support" or "implement X language" - Extending mewt's language capabilities ## When NOT to Use **Don't use for using mewt:** If the user wants to USE mewt for mutation testing (not add language support), use the `mewt` skill instead. **Don't use for grammar development:** This assumes a tree-sitter grammar already exists. If no grammar exists, that's a separate specialized task beyond this skill's scope. ## Essential Principles **Grammar First**: Never attempt to write a tree-sitter grammar from scratch. Always find and verify an existing tree-sitter grammar repository before proceeding. Grammar development requires specialized expertise. **API Simplicity**: The LanguageEngine trait has only 4 methods. Keep implementations minimal - no helper methods, just the trait interface and inline grammar loading. **Resolver First (Dialect-Aware Families)**: If a language family has multiple dialects sharing an extension, resolution must be owned by the registry/resolver layer (precedence, canonical labeling, deterministic ambiguity handling). Do not add command-specific branching for final language+dialect selection. **Canonical Labels Only**: For dialect-aware families, use canonical selectors/labels (e.g., `move`, `move/sui`, `move/iota`) and avoid introducing legacy alias compatibility unless explicitly requested. **Common First**: Start with COMMON_MUTATIONS only. Add language-specific mutations only for unique constructs not covered by common patterns. Most languages need zero custom mutations. **Per-Slug Mutation Tests**: Every mutation slug exposed by the engine must have a dedicated test module under `tests/<language>/mutations/<SLUG>.rs`. The guard test in `tests/languages.rs` enforces this convention. New languages must be wired into the guard before landing. **Integration Conformance First**: Every language `tests/<language>/integration_tests.rs` must run the shared conformance harness from `tests/conformance.rs`, then add language-specific integration assertions as needed. **Example Fixture Policy**: Keep canonical example fixtures at `tests/<language>/example.<ext>` (JavaScript may keep multiple canonical fixtures: `example.js`, `example.ts`, `example.jsx`, `example.tsx`). Integration tests should treat these as smoke checks (`!mutants.is_empty()`), while per-slug tests should use inline fixtures for precise assertions. **Verification Required**: Each phase has explicit exit criteria. Do not proceed to the next phase until all validation passes. ## When to Use - User explicitly requests adding support for a specific programming language - User mentions "add language support" or "new language" - User provides a tree-sitter grammar repository URL - User asks how to extend mewt with new languages ## When NOT to Use - Language already supported (check `src/languages/`) - use existing implementation - User wants to modify existing language support - use general code editing - User wants to add mutations to existing language - edit `src/languages/<lang>/mutations.rs` - No tree-sitter grammar exists - inform user and halt (cannot create grammars) ## Linear Progression ### Phase 1: Grammar Acquisition **Entry Criteria**: User has requested language support **Actions**: 1. Check if tree-sitter grammar repository URL is provided - If not, ask user with common patterns: - `https://github.com/tree-sitter/tree-sitter-<language>` - `https://github.com/<maintainer>/tree-sitter-<language>` - Offer to search the web if needed 2. Once URL confirmed, use Edit to add to `grammars/update.sh` (lines 15-23): ```bash declare -A REPO_URLS=( ["<language>"]="<tree-sitter-repo-url>" ) declare -A GRAMMAR_PATHS=( ["<language>"]="" # or subdirectory if nested ) ``` 3. Run grammar extraction: ```bash cd grammars && bash update.sh <language> false ``` **Exit Criteria**: - [ ] Grammar files exist in `grammars/<language>/src/` - [ ] Must have: `parser.c`, `tree_sitter/` directory - [ ] May have: `scanner.c`, `node-types.json` ### Phase 2: Build System Integration **Entry Criteria**: Grammar files extracted successfully **Actions**: 1. Use Edit to add build configuration to `build.rs` after line 57: ```rust // Build <Language> grammar let <language>_dir: PathBuf = ["grammars", "<language>", "src"].iter().collect(); build_grammar(&<language>_dir, "tree-sitter-<language>"); ``` 2. Verify compilation: ```bash cargo check ``` **Exit Criteria**: - [ ] `cargo check` completes without errors - [ ] Grammar library compiles successfully ### Phase 3: Language Engine Implementation **Entry Criteria**: Grammar builds successfully **Actions**: 1. Create language module structure: ```bash mkdir -p src/languages/<language> ``` 2. Write module declaration (`src/languages/<language>/mod.rs`): ```rust pub mod engine; pub mod mutations; pub mod syntax; ``` 3. Create syntax mappings (`src/languages/<language>/syntax.rs`): - Use Read to examine `grammars/<language>/src/node-types.json` - Map node and field names from the grammar ```rust pub mod nodes { pub const IF_STATEMENT: &str = "if_statement"; pub const RETURN_STATEMENT: &str = "return_statement"; // Add more from node-types.json } pub mod fields { pub const CONDITION: &str = "condition"; pub const ARGUMENTS: &str = "arguments"; } ``` - For every operator-focused slug (AOS, AAOS, BOS, BAOS, LOS, SAOS, COS), confirm which node kinds the grammar uses for those operators (e.g., `augmented_assignment_expression` versus `binary_expression`). Only plug a node kind into `patterns::shuffle_*` after verifying it in `node-types.json`, and include any language-specific operator tokens (such as Go's `&^=` or JavaScript's `**=`/`>>>=`). 4. Create mutations file (`src/languages/<language>/mutations.rs`): ```rust use crate::types::Mutation; pub const <LANGUAGE>_MUTATIONS: &[Mutation] = &[]; ``` 5. Implement engine (`src/languages/<language>/engine.rs`): - Use Read on `src/languages/rust/engine.rs` as reference - Follow the 4-method trait pattern: ```rust use std::sync::OnceLock; use tree_sitter::Language as TsLanguage; use crate::LanguageEngine; use crate::mutations::COMMON_MUTATIONS; use crate::patterns; use crate::types::{Mutant, Mutation, Target}; use crate::utils::{node_text, parse_source}; use super::mutations::<LANGUAGE>_MUTATIONS; use super::syntax::{fields, nodes}; static <LANGUAGE>_LANGUAGE: OnceLock<TsLanguage> = OnceLock::new(); unsafe extern "C" { fn tree_sitter_<language>() -> *const tree_sitter::ffi::TSLanguage; } pub struct <Language>LanguageEngine { mutations: Vec<Mutation>, } impl <Language>LanguageEngine { pub fn new() -> Self { let mut mutations: Vec<Mutation> = Vec::new(); mutations.extend_from_slice(COMMON_MUTATIONS); mutations.extend_from_slice(<LANGUAGE>_MUTATIONS); Self { mutations } } } impl LanguageEngine for <Language>LanguageEngine { fn name(&self) -> &'static str { "<Language>" } fn extensions(&self) -> &[&'static str] { &["<ext>"] } fn get_mutations(&self) -> &[Mutation] { &self.mutations } fn mutate(&self, target: &Target) -> Vec<Mutant> { let source = &target.text; // Load grammar once and cache it let language = <LANGUAGE>_LANGUAGE .get_or_init(|| unsafe { TsLanguage::from_raw(tree_sitter_<language>()) }); let tree = match parse_source(source, language) { Some(t) => t, None => return Vec::new(), }; let root = tree.root_node(); let mut all_mutants = Vec::new(); for m in &self.mutations { match m.slug { "ER" => { all_mutants.extend( patterns::replace( root, source, &[nodes::EXPRESSION_STATEMENT, nodes::RETURN_STATEMENT], "panic!(\"mewt\")", // Use language-appropriate error &|node, src| !node_text(node, src).contains("panic!"), ) .into_iter() .map(|p| Mutant::from_partial(p, target, "ER")), ) } "IF" => { all_mutants.extend( patterns::replace_condition( root, source, nodes::IF_STATEMENT, fields::CONDITION, &["if"], "false", ) .into_iter() .map(|p| Mutant::from_partial(p, target, "IF")), ) } // Add more mutation patterns - see src/languages/rust/engine.rs _ => {} } } all_mutants } } impl Default for <Language>LanguageEngine { fn default() -> Self { Self::new() } } ``` **Exit Criteria**: - [ ] All 4 trait methods implemented - [ ] Grammar loaded inline in `mutate()` (no helper methods) - [ ] At least ER and IF mutations implemented - [ ] Code compiles with `cargo check` ### Phase 4: Language Registration **Dialect-aware note**: If the language can have multiple dialects under one extension, also wire/update registry resolver metadata and canonical labeling rules so command flows consume one resolved selection object. **Entry Criteria**: Engine implementation compiles **Actions**: 1. Use Edit to add module to `src/languages/mod.rs`: ```rust pub mod <language>; ``` 2. Use Edit to register in `src/main.rs` (find the LanguageRegistry section): ```rust registry.register(mewt::languages::<language>::engine::<Language>LanguageEngine::new()); ``` **Exit Criteria**: - [ ] Module exported in `src/languages/mod.rs` - [ ] Engine registered in `src/main.rs` - [ ] `cargo build --release` succeeds ### Phase 5: Tests and Examples For dialect-aware families that share an extension, add: - Resolver integration tests proving deterministic selection for ambiguous extension paths. - Dialect-divergence tests with at least one construct accepted by one dialect and rejected (or treated differently) by another. - Filter/query tests using canonical labels/selectors (no implicit legacy alias guarantees). **Entry Criteria**: Language builds and registers **Actions**: 1. Scaffold the test directories: ```bash mkdir -p tests/<language>/mutations ``` The mutation folder must contain one Rust module per slug (for example `tests/<language>/mutations/AOS.rs`). 2. Write canonical example fixture(s): - Most languages: `tests/<language>/example.<ext>`. - JavaScript-family languages: one canonical fixture per supported extension (`example.js`, `example.ts`, `example.jsx`, `example.tsx`) as needed. - Keep examples syntactically valid and representative, but small enough to stay smoke-test friendly. 3. Wire the language into the integration test entrypoint (`tests/languages.rs`): ```rust mod <language>; ``` 4. Create `tests/<language>/mod.rs` to expose both integration and per-slug suites: ```rust mod integration_tests; mod mutations; ``` 5. Author `tests/<language>/mutations/mod.rs` that re-exports every slug module: ```rust #![allow(non_snake_case)] #[path = "AAOS.rs"] mod aaos; #[path = "AOS.rs"] mod aos; // Repeat for every slug returned by engine.get_mutations() ``` - Use uppercase filenames that match the slug (`<SLUG>.rs`) and map to lowercase module names via `#[path = ...]` where needed. - Keep manual slug wiring in `mutations/mod.rs` in sync with files on disk. 6. For each slug surfaced by `engine.get_mutations()`, create `tests/<language>/mutations/<SLUG>.rs`: - Prefer inline source fixtures for precise, slug-focused assertions. - Use integration helpers (`create_test_target`, shared slug assertion helpers) rather than duplicating setup code. - For operator families (AAOS/BAOS/SAOS/AOS/BOS/COS/LOS), cover all supported operators and include negative cases where useful. 7. Create `tests/<language>/integration_tests.rs` with this pattern: - Define a thin `create_test_target(...)` wrapper that delegates to `tests/utils.rs`. - Run `conformance::run_common_language_checks(...)` for baseline behavior. - Add canonical example-file smoke tests (`!mutants.is_empty()`). - Add only language-specific integration assertions beyond conformance (for example parser edge cases). 8. Update the guard in `tests/languages.rs` so the new language participates in per-slug coverage checks (import the engine and add a `check_language(...)` call). **Exit Criteria**: - [ ] Canonical example fixture(s) created at `tests/<language>/example.<ext>` (or JavaScript `example.*` set) - [ ] Integration and per-slug test modules compiled into `tests/languages.rs` - [ ] (Dialect-aware only) Resolver tests cover deterministic extension resolution and canonical dialect labeling - [ ] (Dialect-aware only) Divergence tests assert meaningful behavior differences between dialects
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub