mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-13 16:15:18 +00:00

Used these commands and some manual searching ``` cargo clippy --fix --all-targets --workspace --exclude gstreamer-player --exclude i-slint-backend-linuxkms --exclude uefi-demo --exclude ffmpeg -- -A clippy::all -W clippy::uninlined_format_args cargo clippy --all-targets -- -A clippy::all -W clippy::uninlined_format_args cargo clippy --fix -- -A clippy::all -W clippy::uninlined_format_args ```
180 lines
5.5 KiB
Rust
180 lines
5.5 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
#![doc = include_str!("README.md")]
|
|
#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
|
|
|
|
extern crate proc_macro;
|
|
use core::str::FromStr;
|
|
use proc_macro::{Delimiter, TokenStream, TokenTree};
|
|
|
|
fn error(e: &str) -> String {
|
|
format!("::core::compile_error!{{\"{e}\"}}")
|
|
}
|
|
|
|
fn generate_test(fn_name: &str, doc: &str, extra_args: usize) -> String {
|
|
if fn_name.is_empty() {
|
|
return error("Could not parse function name");
|
|
}
|
|
|
|
if doc.is_empty() {
|
|
return error("doc comments not found");
|
|
}
|
|
let mut kind = None;
|
|
let idx = match doc.find("```test\n") {
|
|
Some(idx) => idx + 8,
|
|
None => match doc.find("```test,") {
|
|
None => return error("test not found"),
|
|
Some(idx) => match doc[idx..].find('\n') {
|
|
None => return error("test not found"),
|
|
Some(newline) => {
|
|
kind = Some(&doc[idx + 8..idx + newline]);
|
|
idx + newline + 1
|
|
}
|
|
},
|
|
},
|
|
};
|
|
let doc = &doc[(idx)..];
|
|
let idx = match doc.find("```\n") {
|
|
Some(idx) => idx,
|
|
None => return error("end of test not found"),
|
|
};
|
|
let doc = &doc[..idx];
|
|
|
|
let verify = match kind {
|
|
None => String::new(),
|
|
Some(kind) => {
|
|
format!(
|
|
"syntax_nodes::{kind}::verify(SyntaxNode {{
|
|
node: rowan::SyntaxNode::new_root(p.builder.finish()),
|
|
source_file: Default::default(),
|
|
}});",
|
|
)
|
|
}
|
|
};
|
|
|
|
let mut tests = String::new();
|
|
for (i, line) in doc.split('\n').enumerate() {
|
|
let line = line.trim();
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
let follow_args = ", Default::default()".repeat(extra_args);
|
|
tests += &format!(
|
|
r#"
|
|
#[test] fn parser_test_{fn_name}_{i}()
|
|
{{
|
|
let mut diag = Default::default();
|
|
let mut p = DefaultParser::new("{line}", &mut diag);
|
|
{fn_name}(&mut p{follow_args});
|
|
let has_error = p.diags.has_errors();
|
|
//#[cfg(feature = "display-diagnostics")]
|
|
//p.diags.print();
|
|
assert!(!has_error);
|
|
assert_eq!(p.cursor, p.tokens.len());
|
|
{verify}
|
|
}}
|
|
"#,
|
|
)
|
|
}
|
|
tests
|
|
}
|
|
|
|
#[proc_macro_attribute]
|
|
pub fn parser_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
let mut result = item.clone(); // The original function
|
|
|
|
let mut doc = String::new();
|
|
let mut item = item.into_iter();
|
|
|
|
let mut fn_name = String::new();
|
|
let mut extra_args = 0;
|
|
|
|
// Extract the doc comment.
|
|
// Bail out once we find a token that does not fit the doc comment pattern
|
|
loop {
|
|
match item.next() {
|
|
Some(TokenTree::Punct(p)) => {
|
|
if p.as_char() != '#' {
|
|
break;
|
|
}
|
|
}
|
|
Some(TokenTree::Ident(i)) => {
|
|
if i.to_string() == "fn" {
|
|
fn_name = item.next().map_or_else(String::default, |i| i.to_string());
|
|
}
|
|
break;
|
|
}
|
|
_ => break,
|
|
}
|
|
if let Some(TokenTree::Group(g)) = item.next() {
|
|
if g.delimiter() != proc_macro::Delimiter::Bracket {
|
|
break;
|
|
}
|
|
let mut attr = g.stream().into_iter();
|
|
if let Some(TokenTree::Ident(i)) = attr.next() {
|
|
if i.to_string() != "doc" {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
if let Some(TokenTree::Punct(p)) = attr.next() {
|
|
if p.as_char() != '=' {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
if let Some(TokenTree::Literal(lit)) = attr.next() {
|
|
let s = lit.to_string();
|
|
// trim the quotes
|
|
doc += &s[1..(s.len() - 1)];
|
|
doc += "\n";
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if fn_name.is_empty() {
|
|
while let Some(tt) = item.next() {
|
|
if tt.to_string() == "fn" {
|
|
fn_name = item.next().map_or_else(String::default, |i| i.to_string());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
loop {
|
|
match item.next() {
|
|
None => break,
|
|
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
|
|
let mut had_coma = false;
|
|
for tt in g.stream().into_iter() {
|
|
match tt {
|
|
TokenTree::Punct(p) if p.as_char() == ',' => {
|
|
had_coma = true;
|
|
}
|
|
TokenTree::Punct(p) if p.as_char() == ':' && had_coma => {
|
|
extra_args += 1;
|
|
had_coma = false;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
let test_function = TokenStream::from_str(&generate_test(&fn_name, &doc, extra_args))
|
|
.unwrap_or_else(|e| {
|
|
TokenStream::from_str(&error(&format!("Lex error in generated test: {e:?}"))).unwrap()
|
|
});
|
|
|
|
result.extend(test_function);
|
|
result
|
|
}
|