1#[cfg(test)]
21mod codegen;
22
23pub extern crate tree_sitter;
24
25use tree_sitter::Language;
26
27extern "C" {
28 fn tree_sitter_wit() -> Language;
29}
30
31pub fn language() -> Language {
35 unsafe { tree_sitter_wit() }
36}
37
38pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
42
43pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
46#[cfg(test)]
51mod tests {
52 use std::{
53 io::ErrorKind,
54 path::Path,
55 process::{Command, Output},
56 };
57
58 use quote::ToTokens;
59
60 use crate::{codegen, NODE_TYPES};
61
62 #[test]
63 fn test_can_load_grammar() {
64 let mut parser = tree_sitter::Parser::new();
65 parser
66 .set_language(&super::language())
67 .expect("Error loading Wit grammar");
68 }
69
70 #[test]
71 fn run_tree_sitter_tests() {
72 let parser_root = Path::new(env!("CARGO_MANIFEST_DIR"));
73
74 let mut cmd = Command::new("tree-sitter");
75 cmd.arg("test").current_dir(parser_root);
76
77 match cmd.output() {
78 Ok(Output { status, .. }) if status.success() => {}
79 Ok(Output {
80 status,
81 stdout,
82 stderr,
83 }) => {
84 let stdout = String::from_utf8_lossy(&stdout);
85 if !stdout.is_empty() {
86 println!("==== Stdout ====");
87 println!("{stdout}");
88 }
89 let stderr = String::from_utf8_lossy(&stderr);
90 if !stderr.is_empty() {
91 println!("==== Stderr ====");
92 println!("{stderr}");
93 }
94
95 panic!("`{cmd:?}` failed: {status}");
96 }
97 Err(e) if e.kind() == ErrorKind::NotFound => {
98 }
100 Err(e) => {
101 panic!("{e}");
102 }
103 }
104 }
105
106 #[test]
107 fn generate_ast() {
108 let tokens = codegen::generate_ast(NODE_TYPES);
109 let src = format_rust(tokens);
110 let ast_rs = project_root().join("crates/wit-compiler/src/ast/generated.rs");
111 ensure_file_contents(ast_rs, src);
112 }
113
114 pub fn project_root() -> &'static Path {
116 Path::new(env!("CARGO_MANIFEST_DIR"))
117 .ancestors()
118 .find(|p| p.join(".git").exists())
119 .unwrap()
120 }
121
122 pub fn format_rust(contents: impl ToTokens) -> String {
128 let contents = syn::parse2(contents.to_token_stream())
129 .expect("Unable to parse the tokens as a syn::File");
130 prettyplease::unparse(&contents)
131 }
132
133 pub fn ensure_file_contents(path: impl AsRef<Path>, contents: impl AsRef<str>) {
138 let path = path.as_ref();
139 let contents = normalize_newlines(contents.as_ref());
140
141 if let Ok(old_contents) = std::fs::read_to_string(path) {
142 if contents == normalize_newlines(&old_contents) {
143 return;
145 }
146 }
147
148 let display_path = path.strip_prefix(project_root()).unwrap_or(path);
149
150 eprintln!("{} was not up-to-date, updating...", display_path.display());
151
152 if std::env::var("CI").is_ok() {
153 eprintln!("Note: run `cargo test` locally and commit the updated files");
154 }
155
156 if let Some(parent) = path.parent() {
157 let _ = std::fs::create_dir_all(parent);
158 }
159 std::fs::write(path, contents).unwrap();
160 panic!("some file was not up to date and has been updated. Please re-run the tests.");
161 }
162
163 fn normalize_newlines(s: &str) -> String {
164 s.replace("\r\n", "\n")
165 }
166}