#![allow(clippy::unwrap_used)]
use std::path::{Path, PathBuf};
use idet_core::syntax::{Class, Syntax};
fn language(name: &str, rules: &str) -> (Syntax, PathBuf) {
let path = std::env::temp_dir().join(format!("idet-syntax-{name}.idet"));
std::fs::write(&path, rules).unwrap();
(Syntax::load(&path).unwrap(), path)
}
fn classes(syntax: &Syntax, text: &str) -> Vec<(String, Class)> {
syntax
.spans(text)
.into_iter()
.map(|(range, class)| (text.get(range).unwrap_or_default().to_owned(), class))
.collect()
}
const RUST: &str = "extensions rs\nline_comment //\nblock_comment /* */ nested\nstrings \" \\\nkeywords fn let\ntypes u8 String\n";
#[test]
fn words_split_into_keywords_types_and_numbers() {
let (syntax, path) = language("words", RUST);
assert_eq!(
classes(&syntax, "let count: u8 = 42;"),
[
("let".to_owned(), Class::Keyword),
("u8".to_owned(), Class::Type),
("42".to_owned(), Class::Number),
]
);
let _ = std::fs::remove_file(path);
}
#[test]
fn a_digit_inside_a_word_is_not_a_number() {
let (syntax, path) = language("identifier", RUST);
assert!(classes(&syntax, "let u8x2 = value1;")[1..].is_empty());
let _ = std::fs::remove_file(path);
}
#[test]
fn a_block_comment_spans_lines_and_nests() {
let (syntax, path) = language("block", RUST);
let text = "/* outer\n/* inner */\nstill */ let";
assert_eq!(
classes(&syntax, text),
[
("/* outer\n/* inner */\nstill */".to_owned(), Class::Comment),
("let".to_owned(), Class::Keyword),
]
);
let _ = std::fs::remove_file(path);
}
#[test]
fn keywords_inside_comments_and_strings_stay_covered() {
let (syntax, path) = language("cover", RUST);
assert_eq!(
classes(&syntax, "// let\n\"let\"\nlet"),
[
("// let".to_owned(), Class::Comment),
("\"let\"".to_owned(), Class::String),
("let".to_owned(), Class::Keyword),
]
);
let _ = std::fs::remove_file(path);
}
#[test]
fn an_escaped_quote_does_not_end_the_string() {
let (syntax, path) = language("escape", RUST);
assert_eq!(
classes(&syntax, "\"a\\\"b\" let"),
[
("\"a\\\"b\"".to_owned(), Class::String),
("let".to_owned(), Class::Keyword),
]
);
let _ = std::fs::remove_file(path);
}
#[test]
fn an_unclosed_string_runs_to_the_end() {
let (syntax, path) = language("unclosed", RUST);
assert_eq!(
classes(&syntax, "\"open"),
[("\"open".to_owned(), Class::String)]
);
let _ = std::fs::remove_file(path);
}
#[test]
fn spans_are_byte_ranges_across_multibyte_text() {
let (syntax, path) = language("multibyte", RUST);
let text = "// รค\nlet";
assert_eq!(
syntax.spans(text),
[(0..5, Class::Comment), (6..9, Class::Keyword)]
);
let _ = std::fs::remove_file(path);
}
#[test]
fn a_rule_without_its_words_is_an_error() {
let path = std::env::temp_dir().join("idet-syntax-broken.idet");
std::fs::write(&path, "block_comment /*\n").unwrap();
assert!(Syntax::load(&path).is_err());
let _ = std::fs::remove_file(path);
}
#[test]
fn the_shipped_rust_language_covers_rust_files() {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("syntax")
.join("rust.idet");
let syntax = Syntax::load(&path).unwrap();
assert!(syntax.covers("rs"));
assert!(!syntax.covers("txt"));
assert_eq!(
classes(&syntax, "pub fn main() -> Result<(), String> {"),
[
("pub".to_owned(), Class::Keyword),
("fn".to_owned(), Class::Keyword),
("Result".to_owned(), Class::Type),
("String".to_owned(), Class::Type),
]
);
}