use compact_str::CompactString;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DirectiveKind {
Todo,
Fixme,
Expected,
Docs,
IgnoreStart,
IgnoreEnd,
Level,
Deprecated,
DevOnly,
Forget,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DirectiveSeverity {
Warn,
Error,
Off,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VizeDirective {
pub kind: DirectiveKind,
pub payload: CompactString,
pub line: u32,
pub offset: u32,
}
pub fn parse_vize_directive(content: &str, line: u32, offset: u32) -> Option<VizeDirective> {
let trimmed = content.trim();
let rest = trimmed.strip_prefix("@vize:")?;
let (keyword, payload) = match rest.find(|c: char| c.is_whitespace()) {
Some(pos) => (&rest[..pos], rest[pos..].trim()),
None => (rest, ""),
};
let kind = match keyword {
"todo" => DirectiveKind::Todo,
"fixme" => DirectiveKind::Fixme,
"expected" => DirectiveKind::Expected,
"docs" => DirectiveKind::Docs,
"ignore-start" => DirectiveKind::IgnoreStart,
"ignore-end" => DirectiveKind::IgnoreEnd,
s if s.starts_with("level(") && s.ends_with(')') => DirectiveKind::Level,
"deprecated" => DirectiveKind::Deprecated,
"dev-only" => DirectiveKind::DevOnly,
"forget" => DirectiveKind::Forget,
_ => DirectiveKind::Unknown,
};
let payload = if kind == DirectiveKind::Level {
CompactString::from(keyword)
} else {
CompactString::from(payload)
};
Some(VizeDirective {
kind,
payload,
line,
offset,
})
}
pub fn parse_level_severity(payload: &str) -> Option<DirectiveSeverity> {
let inner = payload.strip_prefix("level(")?.strip_suffix(')')?;
match inner {
"warn" => Some(DirectiveSeverity::Warn),
"error" => Some(DirectiveSeverity::Error),
"off" => Some(DirectiveSeverity::Off),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{DirectiveKind, DirectiveSeverity, parse_level_severity, parse_vize_directive};
#[test]
fn test_parse_todo() {
let d = parse_vize_directive(" @vize:todo fix this later ", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Todo);
assert_eq!(d.payload.as_str(), "fix this later");
assert_eq!(d.line, 1);
}
#[test]
fn test_parse_fixme() {
let d = parse_vize_directive("@vize:fixme broken layout", 5, 100).unwrap();
assert_eq!(d.kind, DirectiveKind::Fixme);
assert_eq!(d.payload.as_str(), "broken layout");
}
#[test]
fn test_parse_expected() {
let d = parse_vize_directive("@vize:expected", 10, 200).unwrap();
assert_eq!(d.kind, DirectiveKind::Expected);
assert_eq!(d.payload.as_str(), "");
}
#[test]
fn test_parse_docs() {
let d = parse_vize_directive("@vize:docs Component documentation text", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Docs);
assert_eq!(d.payload.as_str(), "Component documentation text");
}
#[test]
fn test_parse_ignore_start() {
let d = parse_vize_directive("@vize:ignore-start", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::IgnoreStart);
assert_eq!(d.payload.as_str(), "");
}
#[test]
fn test_parse_ignore_end() {
let d = parse_vize_directive("@vize:ignore-end", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::IgnoreEnd);
assert_eq!(d.payload.as_str(), "");
}
#[test]
fn test_parse_level_warn() {
let d = parse_vize_directive("@vize:level(warn)", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Level);
assert_eq!(
parse_level_severity(&d.payload),
Some(DirectiveSeverity::Warn)
);
}
#[test]
fn test_parse_level_error() {
let d = parse_vize_directive("@vize:level(error)", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Level);
assert_eq!(
parse_level_severity(&d.payload),
Some(DirectiveSeverity::Error)
);
}
#[test]
fn test_parse_level_off() {
let d = parse_vize_directive("@vize:level(off)", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Level);
assert_eq!(
parse_level_severity(&d.payload),
Some(DirectiveSeverity::Off)
);
}
#[test]
fn test_parse_level_invalid() {
let d = parse_vize_directive("@vize:level(invalid)", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Level);
assert_eq!(parse_level_severity(&d.payload), None);
}
#[test]
fn test_parse_deprecated() {
let d = parse_vize_directive("@vize:deprecated use NewComp instead", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Deprecated);
assert_eq!(d.payload.as_str(), "use NewComp instead");
}
#[test]
fn test_parse_dev_only() {
let d = parse_vize_directive("@vize:dev-only", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::DevOnly);
}
#[test]
fn test_parse_unknown() {
let d = parse_vize_directive("@vize:foobar something", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Unknown);
}
#[test]
fn test_not_a_directive() {
assert!(parse_vize_directive("just a normal comment", 1, 0).is_none());
assert!(parse_vize_directive("@vize without colon", 1, 0).is_none());
assert!(parse_vize_directive("", 1, 0).is_none());
}
#[test]
fn test_whitespace_handling() {
let d = parse_vize_directive(" @vize:todo lots of spaces ", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Todo);
assert_eq!(d.payload.as_str(), "lots of spaces");
}
#[test]
fn test_todo_no_message() {
let d = parse_vize_directive("@vize:todo", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Todo);
assert_eq!(d.payload.as_str(), "");
}
#[test]
fn test_parse_forget() {
let d = parse_vize_directive("@vize:forget shiki output is pre-escaped", 3, 50).unwrap();
assert_eq!(d.kind, DirectiveKind::Forget);
assert_eq!(d.payload.as_str(), "shiki output is pre-escaped");
assert_eq!(d.line, 3);
}
#[test]
fn test_parse_forget_no_reason() {
let d = parse_vize_directive("@vize:forget", 1, 0).unwrap();
assert_eq!(d.kind, DirectiveKind::Forget);
assert_eq!(d.payload.as_str(), "");
}
}