{"id":51514688,"url":"https://github.com/jamesgober/test-lang","last_synced_at":"2026-07-08T10:01:39.574Z","repository":{"id":368624858,"uuid":"1274274002","full_name":"jamesgober/test-lang","owner":"jamesgober","description":"Compiler snapshot test harness for tokens, ASTs, and errors.","archived":false,"fork":false,"pushed_at":"2026-07-01T12:41:10.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T13:25:15.149Z","etag":null,"topics":["compiler","language","rust","snapshot","testing"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["jamesgober"]}},"created_at":"2026-06-19T10:51:32.000Z","updated_at":"2026-07-01T12:38:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/test-lang","commit_stats":null,"previous_names":["jamesgober/test-lang"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/test-lang","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftest-lang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftest-lang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftest-lang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftest-lang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/test-lang/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftest-lang/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35260671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","language","rust","snapshot","testing"],"created_at":"2026-07-08T10:01:37.262Z","updated_at":"2026-07-08T10:01:39.565Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":["https://github.com/sponsors/jamesgober"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n    \u003cbr\u003e\n    \u003cb\u003etest-lang\u003c/b\u003e\n    \u003cbr\u003e\n    \u003csub\u003e\u003csup\u003eSNAPSHOT HARNESS\u003c/sup\u003e\u003c/sub\u003e\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/test-lang\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/test-lang\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/test-lang\"\u003e\u003cimg alt=\"Downloads\" src=\"https://img.shields.io/crates/d/test-lang?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/test-lang\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/test-lang\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/test-lang/actions\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/jamesgober/test-lang/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md\"\u003e\u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.85%2B-blue\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"left\"\u003e\n    \u003cp\u003e\n        \u003cstrong\u003etest-lang\u003c/strong\u003e is a snapshot test harness for language front-ends. Give it source, run that source through a stage — a lexer, a parser, a diagnostics renderer — and assert the rendered result against a known-good block of text. When the output changes, you get a line-level diff pointing at exactly what moved, and accepting the new behavior is a copy-paste.\n    \u003c/p\u003e\n    \u003cp\u003e\n        It owns no grammar and takes no runtime dependencies. The harness works over anything that renders itself to text — a \u003ccode\u003eDisplay\u003c/code\u003e value, a \u003ccode\u003eDebug\u003c/code\u003e tree, or an iterator of displayable items — so the same two types serve a hand-written lexer, a generated parser, or a diagnostics layer without coupling to any of them. It is \u003ccode\u003eno_std\u003c/code\u003e + \u003ccode\u003ealloc\u003c/code\u003e capable and normalizes line endings and trailing whitespace so a snapshot captured on Windows matches one written on Linux.\n    \u003c/p\u003e\n    \u003cbr\u003e\n    \u003chr\u003e\n    \u003cp\u003e\n        \u003cstrong\u003eMSRV is 1.85+\u003c/strong\u003e (Rust 2024 edition). Part of the \u003ccode\u003e-lang\u003c/code\u003e language-construction family.\n    \u003c/p\u003e\n    \u003cblockquote\u003e\n        \u003cstrong\u003eStable — API frozen at \u003ccode\u003e1.0.0\u003c/code\u003e.\u003c/strong\u003e The public surface will not change in a breaking way before a 2.0; 1.x releases are additive. See \u003ca href=\"./docs/API.md\"\u003e\u003ccode\u003edocs/API.md\u003c/code\u003e\u003c/a\u003e for the frozen surface and \u003ca href=\"./CHANGELOG.md\"\u003e\u003ccode\u003eCHANGELOG.md\u003c/code\u003e\u003c/a\u003e for history.\n    \u003c/blockquote\u003e\n\u003c/div\u003e\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Performance First\n\nLatest local Criterion means (`cargo bench`, Windows x86_64, Rust stable). The workload that matters in a test suite is the check on the matching path — the case a green suite runs on every pass:\n\n- **Capture** (`per_line`, 16 lines): ~0.5 µs\n- **Check, matching** (16 lines): ~1.2 µs\n- **Check, matching** (256 lines): ~19 µs\n\nThe check is a line-level diff with a common prefix/suffix fast path: identical leading and trailing lines are matched in linear time before the quadratic LCS engine runs, so a near-miss snapshot only pays for the region that actually changed.\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Installation\n\n```toml\n[dependencies]\ntest-lang = \"1\"\n```\n\n`no_std` + `alloc` (drops the `std::error::Error` anchor, keeps everything else):\n\n```toml\n[dependencies]\ntest-lang = { version = \"1\", default-features = false }\n```\n\n\u003cbr\u003e\n\n## Quick Start\n\nSnapshot a token stream, one token per line, and assert it:\n\n```rust\nuse test_lang::Snapshot;\n\n// Whatever your lexer produces — here a stand-in that yields display strings.\nfn lex(source: \u0026str) -\u003e Vec\u003cString\u003e {\n    source.split_whitespace().map(str::to_string).collect()\n}\n\nlet snapshot = Snapshot::per_line(lex(\"let x = 1\"));\nsnapshot.check(\"let\\nx\\n=\\n1\").expect(\"token stream matches\");\n```\n\nWhen the output drifts, the returned `Mismatch` shows precisely what changed:\n\n```rust\nuse test_lang::Snapshot;\n\nlet snapshot = Snapshot::per_line([\"let\", \"y\", \"=\", \"1\"]);\nlet err = snapshot.check(\"let\\nx\\n=\\n1\").unwrap_err();\n\n// `-x` was expected; `+y` was produced in its place.\nassert!(err.to_string().contains(\"-x\"));\nassert!(err.to_string().contains(\"+y\"));\n```\n\n\u003cbr\u003e\n\u003chr\u003e\n\n## Features\n\n- **Three capture modes** — `Snapshot::display` for a `Display` value, `Snapshot::debug` for a pretty-printed `Debug` tree, and `Snapshot::per_line` for a token stream (one item per line, so the diff points at the exact token).\n- **Cross-platform normalization** — CRLF/CR collapse to LF, trailing whitespace is stripped, and trailing blank lines are trimmed. A snapshot written by hand in a test matches output captured on any platform.\n- **Line-level diffs** — a failed `check` returns a `Mismatch` whose `Display` is a unified `-expected`/`+actual` diff; `Mismatch::diff` exposes the `Diff` for programmatic inspection.\n- **No runtime dependencies** — built on `core::fmt` and `alloc` only. `no_std` capable.\n- **No panics** — `check` returns `Result`; the test author decides whether to `unwrap`, `expect`, or propagate.\n\n\u003cbr\u003e\n\n## API Overview\n\nFor a complete reference with examples, see [`docs/API.md`](./docs/API.md).\n\n- [`Snapshot`](./docs/API.md#snapshot) — a normalized, comparable rendering of compiler output; `new` / `display` / `debug` / `per_line` / `check`.\n- [`Diff`](./docs/API.md#diff) \u0026 [`Change`](./docs/API.md#change) — the line-level edit script, rendered as a unified diff.\n- [`Mismatch`](./docs/API.md#mismatch) — the error returned by `Snapshot::check`, carrying the `Diff`.\n\nRunnable examples: [`examples/tokens.rs`](./examples/tokens.rs), [`examples/ast.rs`](./examples/ast.rs), [`examples/diagnostics.rs`](./examples/diagnostics.rs).\n\n```bash\ncargo run --example tokens\ncargo run --example ast\ncargo run --example diagnostics\n```\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Contributing\n\nSee [`REPS.md`](./REPS.md) for engineering standards and the definition of done. Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.\n\n\u003cbr\u003e\n\n\u003cdiv id=\"license\"\u003e\n    \u003ch2\u003eLicense\u003c/h2\u003e\n    \u003cp\u003eLicensed under either of\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003cb\u003eApache License, Version 2.0\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-APACHE\"\u003eLICENSE-APACHE\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003cb\u003eMIT License\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-MIT\"\u003eLICENSE-MIT\u003c/a\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003eat your option.\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  \u003csup\u003eCOPYRIGHT \u003csmall\u003e\u0026copy;\u003c/small\u003e 2026 \u003cstrong\u003eJames Gober \u003cme@jamesgober.com\u003e.\u003c/strong\u003e\u003c/sup\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Ftest-lang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Ftest-lang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Ftest-lang/lists"}