mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-04 19:29:07 +00:00
Implement new eBNF for codeblock attributes
This commit is contained in:
parent
4ce17fa30e
commit
7681f63cab
@ -862,19 +862,34 @@ pub(crate) struct TagIterator<'a, 'tcx> {
|
|||||||
extra: Option<&'a ExtraInfo<'tcx>>,
|
extra: Option<&'a ExtraInfo<'tcx>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(crate) enum TokenKind<'a> {
|
pub(crate) enum LangStringToken<'a> {
|
||||||
Token(&'a str),
|
LangToken(&'a str),
|
||||||
Attribute(&'a str),
|
ClassAttribute(&'a str),
|
||||||
|
KeyValueAttribute(&'a str, &'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_bareword_char(c: char) -> bool {
|
||||||
|
c == '_' || c == '-' || c == ':' || c.is_ascii_alphabetic() || c.is_ascii_digit()
|
||||||
|
}
|
||||||
fn is_separator(c: char) -> bool {
|
fn is_separator(c: char) -> bool {
|
||||||
c == ' ' || c == ',' || c == '\t'
|
c == ' ' || c == ',' || c == '\t'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Indices {
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> TagIterator<'a, 'tcx> {
|
impl<'a, 'tcx> TagIterator<'a, 'tcx> {
|
||||||
pub(crate) fn new(data: &'a str, extra: Option<&'a ExtraInfo<'tcx>>) -> Self {
|
pub(crate) fn new(data: &'a str, extra: Option<&'a ExtraInfo<'tcx>>) -> Self {
|
||||||
Self { inner: data.char_indices().peekable(), data, extra, is_in_attribute_block: false }
|
Self { inner: data.char_indices().peekable(), data, is_in_attribute_block: false, extra }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_error(&self, err: &str) {
|
||||||
|
if let Some(extra) = self.extra {
|
||||||
|
extra.error_invalid_codeblock_attr(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_separators(&mut self) -> Option<usize> {
|
fn skip_separators(&mut self) -> Option<usize> {
|
||||||
@ -887,84 +902,183 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_error(&self, err: &str) {
|
fn parse_string(&mut self, start: usize) -> Option<Indices> {
|
||||||
if let Some(extra) = self.extra {
|
while let Some((pos, c)) = self.inner.next() {
|
||||||
extra.error_invalid_codeblock_attr(err);
|
if c == '"' {
|
||||||
|
return Some(Indices { start: start + 1, end: pos });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.emit_error("unclosed quote string `\"`");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_class(&mut self, start: usize) -> Option<LangStringToken<'a>> {
|
||||||
|
while let Some((pos, c)) = self.inner.peek().copied() {
|
||||||
|
if is_bareword_char(c) {
|
||||||
|
self.inner.next();
|
||||||
|
} else {
|
||||||
|
let class = &self.data[start + 1..pos];
|
||||||
|
if class.is_empty() {
|
||||||
|
self.emit_error(&format!("unexpected `{c}` character after `.`"));
|
||||||
|
return None;
|
||||||
|
} else if self.check_after_token() {
|
||||||
|
return Some(LangStringToken::ClassAttribute(class));
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let class = &self.data[start + 1..];
|
||||||
|
if class.is_empty() {
|
||||||
|
self.emit_error("missing character after `.`");
|
||||||
|
None
|
||||||
|
} else if self.check_after_token() {
|
||||||
|
Some(LangStringToken::ClassAttribute(class))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns false if the string is unfinished.
|
fn parse_token(&mut self, start: usize) -> Option<Indices> {
|
||||||
fn skip_string(&mut self) -> bool {
|
while let Some((pos, c)) = self.inner.peek() {
|
||||||
|
if !is_bareword_char(*c) {
|
||||||
|
return Some(Indices { start, end: *pos });
|
||||||
|
}
|
||||||
|
self.inner.next();
|
||||||
|
}
|
||||||
|
self.emit_error("unexpected end");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_key_value(&mut self, c: char, start: usize) -> Option<LangStringToken<'a>> {
|
||||||
|
let key_indices =
|
||||||
|
if c == '"' { self.parse_string(start)? } else { self.parse_token(start)? };
|
||||||
|
if key_indices.start == key_indices.end {
|
||||||
|
self.emit_error("unexpected empty string as key");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_, c)) = self.inner.next() {
|
||||||
|
if c != '=' {
|
||||||
|
self.emit_error(&format!("expected `=`, found `{}`", c));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.emit_error("unexpected end");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let value_indices = match self.inner.next() {
|
||||||
|
Some((pos, '"')) => self.parse_string(pos)?,
|
||||||
|
Some((pos, c)) if is_bareword_char(c) => self.parse_token(pos)?,
|
||||||
|
Some((_, c)) => {
|
||||||
|
self.emit_error(&format!("unexpected `{c}` character after `=`"));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.emit_error("expected value after `=`");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if value_indices.start == value_indices.end {
|
||||||
|
self.emit_error("unexpected empty string as value");
|
||||||
|
None
|
||||||
|
} else if self.check_after_token() {
|
||||||
|
Some(LangStringToken::KeyValueAttribute(
|
||||||
|
&self.data[key_indices.start..key_indices.end],
|
||||||
|
&self.data[value_indices.start..value_indices.end],
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `false` if an error was emitted.
|
||||||
|
fn check_after_token(&mut self) -> bool {
|
||||||
|
if let Some((_, c)) = self.inner.peek().copied() {
|
||||||
|
if c == '}' || is_separator(c) || c == '(' {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.emit_error(&format!("unexpected `{c}` character"));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The error will be caught on the next iteration.
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_in_attribute_block(&mut self) -> Option<LangStringToken<'a>> {
|
||||||
|
while let Some((pos, c)) = self.inner.next() {
|
||||||
|
if c == '}' {
|
||||||
|
self.is_in_attribute_block = false;
|
||||||
|
return self.next();
|
||||||
|
} else if c == '.' {
|
||||||
|
return self.parse_class(pos);
|
||||||
|
} else if c == '"' || is_bareword_char(c) {
|
||||||
|
return self.parse_key_value(c, pos);
|
||||||
|
} else {
|
||||||
|
self.emit_error(&format!("unexpected character `{c}`"));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `false` if an error was emitted.
|
||||||
|
fn skip_paren_block(&mut self) -> bool {
|
||||||
while let Some((_, c)) = self.inner.next() {
|
while let Some((_, c)) = self.inner.next() {
|
||||||
if c == '"' {
|
if c == ')' {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.emit_error("unclosed quote string: missing `\"` at the end");
|
self.emit_error("unclosed comment: missing `)` at the end");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_in_attribute_block(&mut self, start: usize) -> Option<TokenKind<'a>> {
|
fn parse_outside_attribute_block(&mut self, start: usize) -> Option<LangStringToken<'a>> {
|
||||||
while let Some((pos, c)) = self.inner.next() {
|
while let Some((pos, c)) = self.inner.next() {
|
||||||
if is_separator(c) {
|
if c == '"' {
|
||||||
return Some(TokenKind::Attribute(&self.data[start..pos]));
|
if pos != start {
|
||||||
} else if c == '{' {
|
self.emit_error("expected ` `, `{` or `,` found `\"`");
|
||||||
// There shouldn't be a nested block!
|
return None;
|
||||||
self.emit_error("unexpected `{` inside attribute block (`{}`)");
|
|
||||||
let attr = &self.data[start..pos];
|
|
||||||
if attr.is_empty() {
|
|
||||||
return self.next();
|
|
||||||
}
|
}
|
||||||
self.inner.next();
|
let indices = self.parse_string(pos)?;
|
||||||
return Some(TokenKind::Attribute(attr));
|
if let Some((_, c)) = self.inner.peek().copied() && c != '{' && !is_separator(c) && c != '(' {
|
||||||
} else if c == '}' {
|
self.emit_error(&format!("expected ` `, `{{` or `,` after `\"`, found `{c}`"));
|
||||||
self.is_in_attribute_block = false;
|
return None;
|
||||||
let attr = &self.data[start..pos];
|
|
||||||
if attr.is_empty() {
|
|
||||||
return self.next();
|
|
||||||
}
|
}
|
||||||
return Some(TokenKind::Attribute(attr));
|
return Some(LangStringToken::LangToken(&self.data[indices.start..indices.end]));
|
||||||
} else if c == '"' && !self.skip_string() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Unclosed attribute block!
|
|
||||||
self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
|
|
||||||
let token = &self.data[start..];
|
|
||||||
if token.is_empty() { None } else { Some(TokenKind::Attribute(token)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_outside_attribute_block(&mut self, start: usize) -> Option<TokenKind<'a>> {
|
|
||||||
while let Some((pos, c)) = self.inner.next() {
|
|
||||||
if is_separator(c) {
|
|
||||||
return Some(TokenKind::Token(&self.data[start..pos]));
|
|
||||||
} else if c == '{' {
|
} else if c == '{' {
|
||||||
self.is_in_attribute_block = true;
|
self.is_in_attribute_block = true;
|
||||||
let token = &self.data[start..pos];
|
return self.next();
|
||||||
if token.is_empty() {
|
} else if is_bareword_char(c) {
|
||||||
return self.next();
|
continue;
|
||||||
|
} else if is_separator(c) {
|
||||||
|
if pos != start {
|
||||||
|
return Some(LangStringToken::LangToken(&self.data[start..pos]));
|
||||||
}
|
}
|
||||||
return Some(TokenKind::Token(token));
|
return self.next();
|
||||||
} else if c == '}' {
|
} else if c == '(' {
|
||||||
// We're not in a block so it shouldn't be there!
|
if !self.skip_paren_block() {
|
||||||
self.emit_error("unexpected `}` outside attribute block (`{}`)");
|
return None;
|
||||||
let token = &self.data[start..pos];
|
|
||||||
if token.is_empty() {
|
|
||||||
return self.next();
|
|
||||||
}
|
}
|
||||||
self.inner.next();
|
if pos != start {
|
||||||
return Some(TokenKind::Attribute(token));
|
return Some(LangStringToken::LangToken(&self.data[start..pos]));
|
||||||
} else if c == '"' && !self.skip_string() {
|
}
|
||||||
|
return self.next();
|
||||||
|
} else {
|
||||||
|
self.emit_error(&format!("unexpected character `{c}`"));
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let token = &self.data[start..];
|
let token = &self.data[start..];
|
||||||
if token.is_empty() { None } else { Some(TokenKind::Token(token)) }
|
if token.is_empty() { None } else { Some(LangStringToken::LangToken(&self.data[start..])) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
|
impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
|
||||||
type Item = TokenKind<'a>;
|
type Item = LangStringToken<'a>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let Some(start) = self.skip_separators() else {
|
let Some(start) = self.skip_separators() else {
|
||||||
@ -974,7 +1088,7 @@ impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if self.is_in_attribute_block {
|
if self.is_in_attribute_block {
|
||||||
self.parse_in_attribute_block(start)
|
self.parse_in_attribute_block()
|
||||||
} else {
|
} else {
|
||||||
self.parse_outside_attribute_block(start)
|
self.parse_outside_attribute_block(start)
|
||||||
}
|
}
|
||||||
@ -999,16 +1113,6 @@ impl Default for LangString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_class(class: &str, after: &str, data: &mut LangString, extra: Option<&ExtraInfo<'_>>) {
|
|
||||||
if class.is_empty() {
|
|
||||||
if let Some(extra) = extra {
|
|
||||||
extra.error_invalid_codeblock_attr(&format!("missing class name after `{after}`"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data.added_classes.push(class.replace('"', ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LangString {
|
impl LangString {
|
||||||
fn parse_without_check(
|
fn parse_without_check(
|
||||||
string: &str,
|
string: &str,
|
||||||
@ -1034,41 +1138,41 @@ impl LangString {
|
|||||||
|
|
||||||
for token in TagIterator::new(string, extra) {
|
for token in TagIterator::new(string, extra) {
|
||||||
match token {
|
match token {
|
||||||
TokenKind::Token("should_panic") => {
|
LangStringToken::LangToken("should_panic") => {
|
||||||
data.should_panic = true;
|
data.should_panic = true;
|
||||||
seen_rust_tags = !seen_other_tags;
|
seen_rust_tags = !seen_other_tags;
|
||||||
}
|
}
|
||||||
TokenKind::Token("no_run") => {
|
LangStringToken::LangToken("no_run") => {
|
||||||
data.no_run = true;
|
data.no_run = true;
|
||||||
seen_rust_tags = !seen_other_tags;
|
seen_rust_tags = !seen_other_tags;
|
||||||
}
|
}
|
||||||
TokenKind::Token("ignore") => {
|
LangStringToken::LangToken("ignore") => {
|
||||||
data.ignore = Ignore::All;
|
data.ignore = Ignore::All;
|
||||||
seen_rust_tags = !seen_other_tags;
|
seen_rust_tags = !seen_other_tags;
|
||||||
}
|
}
|
||||||
TokenKind::Token(x) if x.starts_with("ignore-") => {
|
LangStringToken::LangToken(x) if x.starts_with("ignore-") => {
|
||||||
if enable_per_target_ignores {
|
if enable_per_target_ignores {
|
||||||
ignores.push(x.trim_start_matches("ignore-").to_owned());
|
ignores.push(x.trim_start_matches("ignore-").to_owned());
|
||||||
seen_rust_tags = !seen_other_tags;
|
seen_rust_tags = !seen_other_tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Token("rust") => {
|
LangStringToken::LangToken("rust") => {
|
||||||
data.rust = true;
|
data.rust = true;
|
||||||
seen_rust_tags = true;
|
seen_rust_tags = true;
|
||||||
}
|
}
|
||||||
TokenKind::Token("test_harness") => {
|
LangStringToken::LangToken("test_harness") => {
|
||||||
data.test_harness = true;
|
data.test_harness = true;
|
||||||
seen_rust_tags = !seen_other_tags || seen_rust_tags;
|
seen_rust_tags = !seen_other_tags || seen_rust_tags;
|
||||||
}
|
}
|
||||||
TokenKind::Token("compile_fail") => {
|
LangStringToken::LangToken("compile_fail") => {
|
||||||
data.compile_fail = true;
|
data.compile_fail = true;
|
||||||
seen_rust_tags = !seen_other_tags || seen_rust_tags;
|
seen_rust_tags = !seen_other_tags || seen_rust_tags;
|
||||||
data.no_run = true;
|
data.no_run = true;
|
||||||
}
|
}
|
||||||
TokenKind::Token(x) if x.starts_with("edition") => {
|
LangStringToken::LangToken(x) if x.starts_with("edition") => {
|
||||||
data.edition = x[7..].parse::<Edition>().ok();
|
data.edition = x[7..].parse::<Edition>().ok();
|
||||||
}
|
}
|
||||||
TokenKind::Token(x)
|
LangStringToken::LangToken(x)
|
||||||
if allow_error_code_check && x.starts_with('E') && x.len() == 5 =>
|
if allow_error_code_check && x.starts_with('E') && x.len() == 5 =>
|
||||||
{
|
{
|
||||||
if x[1..].parse::<u32>().is_ok() {
|
if x[1..].parse::<u32>().is_ok() {
|
||||||
@ -1078,7 +1182,7 @@ impl LangString {
|
|||||||
seen_other_tags = true;
|
seen_other_tags = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Token(x) if extra.is_some() => {
|
LangStringToken::LangToken(x) if extra.is_some() => {
|
||||||
let s = x.to_lowercase();
|
let s = x.to_lowercase();
|
||||||
if let Some((flag, help)) = if s == "compile-fail"
|
if let Some((flag, help)) = if s == "compile-fail"
|
||||||
|| s == "compile_fail"
|
|| s == "compile_fail"
|
||||||
@ -1120,22 +1224,24 @@ impl LangString {
|
|||||||
seen_other_tags = true;
|
seen_other_tags = true;
|
||||||
data.unknown.push(x.to_owned());
|
data.unknown.push(x.to_owned());
|
||||||
}
|
}
|
||||||
TokenKind::Token(x) => {
|
LangStringToken::LangToken(x) => {
|
||||||
seen_other_tags = true;
|
seen_other_tags = true;
|
||||||
data.unknown.push(x.to_owned());
|
data.unknown.push(x.to_owned());
|
||||||
}
|
}
|
||||||
TokenKind::Attribute(attr) => {
|
LangStringToken::KeyValueAttribute(key, value) => {
|
||||||
seen_other_tags = true;
|
seen_other_tags = true;
|
||||||
if let Some(class) = attr.strip_prefix('.') {
|
if key == "class" {
|
||||||
handle_class(class, ".", &mut data, extra);
|
data.added_classes.push(value.to_owned());
|
||||||
} else if let Some(class) = attr.strip_prefix("class=") {
|
|
||||||
handle_class(class, "class=", &mut data, extra);
|
|
||||||
} else if let Some(extra) = extra {
|
} else if let Some(extra) = extra {
|
||||||
extra.error_invalid_codeblock_attr(&format!(
|
extra.error_invalid_codeblock_attr(&format!(
|
||||||
"unsupported attribute `{attr}`"
|
"unsupported attribute `{key}`"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LangStringToken::ClassAttribute(class) => {
|
||||||
|
seen_other_tags = true;
|
||||||
|
data.added_classes.push(class.to_owned());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
|
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
|
||||||
use super::{
|
use super::{
|
||||||
ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownItemInfo, TagIterator,
|
ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, LangStringToken, Markdown,
|
||||||
TokenKind,
|
MarkdownItemInfo, TagIterator,
|
||||||
};
|
};
|
||||||
use rustc_span::edition::{Edition, DEFAULT_EDITION};
|
use rustc_span::edition::{Edition, DEFAULT_EDITION};
|
||||||
|
|
||||||
@ -55,12 +55,13 @@ fn test_lang_string_parse() {
|
|||||||
t(Default::default());
|
t(Default::default());
|
||||||
t(LangString { original: "rust".into(), ..Default::default() });
|
t(LangString { original: "rust".into(), ..Default::default() });
|
||||||
t(LangString {
|
t(LangString {
|
||||||
original: ".rust".into(),
|
original: "rusta".into(),
|
||||||
rust: false,
|
rust: false,
|
||||||
unknown: vec![".rust".into()],
|
unknown: vec!["rusta".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
t(LangString { original: "{rust}".into(), rust: false, ..Default::default() });
|
// error
|
||||||
|
t(LangString { original: "{rust}".into(), rust: true, ..Default::default() });
|
||||||
t(LangString {
|
t(LangString {
|
||||||
original: "{.rust}".into(),
|
original: "{.rust}".into(),
|
||||||
rust: false,
|
rust: false,
|
||||||
@ -107,9 +108,9 @@ fn test_lang_string_parse() {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
t(LangString {
|
t(LangString {
|
||||||
original: "test_harness,.rust".into(),
|
original: "test_harness,rusta".into(),
|
||||||
test_harness: true,
|
test_harness: true,
|
||||||
unknown: vec![".rust".into()],
|
unknown: vec!["rusta".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
t(LangString {
|
t(LangString {
|
||||||
@ -194,65 +195,51 @@ fn test_lang_string_parse() {
|
|||||||
unknown: vec!["unknown".into()],
|
unknown: vec!["unknown".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
t(LangString {
|
// error
|
||||||
original: "{.first.second}".into(),
|
t(LangString { original: "{.first.second}".into(), rust: true, ..Default::default() });
|
||||||
added_classes: vec!["first.second".into()],
|
// error
|
||||||
rust: false,
|
t(LangString { original: "{class=first=second}".into(), rust: true, ..Default::default() });
|
||||||
..Default::default()
|
// error
|
||||||
});
|
t(LangString { original: "{class=first.second}".into(), rust: true, ..Default::default() });
|
||||||
t(LangString {
|
// error
|
||||||
original: "{class=first=second}".into(),
|
t(LangString { original: "{class=.first}".into(), rust: true, ..Default::default() });
|
||||||
added_classes: vec!["first=second".into()],
|
|
||||||
rust: false,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
t(LangString {
|
|
||||||
original: "{class=first.second}".into(),
|
|
||||||
added_classes: vec!["first.second".into()],
|
|
||||||
rust: false,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
t(LangString {
|
|
||||||
original: "{class=.first}".into(),
|
|
||||||
added_classes: vec![".first".into()],
|
|
||||||
rust: false,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
t(LangString {
|
t(LangString {
|
||||||
original: r#"{class="first"}"#.into(),
|
original: r#"{class="first"}"#.into(),
|
||||||
added_classes: vec!["first".into()],
|
added_classes: vec!["first".into()],
|
||||||
rust: false,
|
rust: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
t(LangString {
|
// error
|
||||||
original: r#"{class=f"irst"}"#.into(),
|
t(LangString { original: r#"{class=f"irst"}"#.into(), rust: true, ..Default::default() });
|
||||||
added_classes: vec!["first".into()],
|
|
||||||
rust: false,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lang_string_tokenizer() {
|
fn test_lang_string_tokenizer() {
|
||||||
fn case(lang_string: &str, want: &[TokenKind<'_>]) {
|
fn case(lang_string: &str, want: &[LangStringToken<'_>]) {
|
||||||
let have = TagIterator::new(lang_string, None).collect::<Vec<_>>();
|
let have = TagIterator::new(lang_string, None).collect::<Vec<_>>();
|
||||||
assert_eq!(have, want, "Unexpected lang string split for `{}`", lang_string);
|
assert_eq!(have, want, "Unexpected lang string split for `{}`", lang_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
case("", &[]);
|
case("", &[]);
|
||||||
case("foo", &[TokenKind::Token("foo")]);
|
case("foo", &[LangStringToken::LangToken("foo")]);
|
||||||
case("foo,bar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
|
case("foo,bar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]);
|
||||||
case(".foo,.bar", &[TokenKind::Token(".foo"), TokenKind::Token(".bar")]);
|
case(".foo,.bar", &[]);
|
||||||
case("{.foo,.bar}", &[TokenKind::Attribute(".foo"), TokenKind::Attribute(".bar")]);
|
case(
|
||||||
case(" {.foo,.bar} ", &[TokenKind::Attribute(".foo"), TokenKind::Attribute(".bar")]);
|
"{.foo,.bar}",
|
||||||
case("foo bar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
|
&[LangStringToken::ClassAttribute("foo"), LangStringToken::ClassAttribute("bar")],
|
||||||
case("foo\tbar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
|
);
|
||||||
case("foo\t, bar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
|
case(
|
||||||
case(" foo , bar ", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
|
" {.foo,.bar} ",
|
||||||
case(",,foo,,bar,,", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
|
&[LangStringToken::ClassAttribute("foo"), LangStringToken::ClassAttribute("bar")],
|
||||||
case("foo=bar", &[TokenKind::Token("foo=bar")]);
|
);
|
||||||
case("a-b-c", &[TokenKind::Token("a-b-c")]);
|
case("foo bar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]);
|
||||||
case("a_b_c", &[TokenKind::Token("a_b_c")]);
|
case("foo\tbar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]);
|
||||||
|
case("foo\t, bar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]);
|
||||||
|
case(" foo , bar ", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]);
|
||||||
|
case(",,foo,,bar,,", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]);
|
||||||
|
case("foo=bar", &[]);
|
||||||
|
case("a-b-c", &[LangStringToken::LangToken("a-b-c")]);
|
||||||
|
case("a_b_c", &[LangStringToken::LangToken("a_b_c")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -6,14 +6,80 @@
|
|||||||
#![feature(no_core)]
|
#![feature(no_core)]
|
||||||
#![no_core]
|
#![no_core]
|
||||||
|
|
||||||
/// ```{. class= whatever=hehe #id} } {{
|
/// ```{. }
|
||||||
/// main;
|
/// main;
|
||||||
/// ```
|
/// ```
|
||||||
//~^^^ ERROR missing class name after `.`
|
//~^^^ ERROR unexpected ` ` character after `.`
|
||||||
//~| ERROR missing class name after `class=`
|
|
||||||
//~| ERROR unsupported attribute `whatever=hehe`
|
|
||||||
//~| ERROR unsupported attribute `#id`
|
|
||||||
//~| ERROR unexpected `}` outside attribute block (`{}`)
|
|
||||||
//~| ERROR unclosed attribute block (`{}`): missing `}` at the end
|
|
||||||
//~| ERROR unexpected `{` inside attribute block (`{}`)
|
|
||||||
pub fn foo() {}
|
pub fn foo() {}
|
||||||
|
|
||||||
|
/// ```{class= a}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected ` ` character after `=`
|
||||||
|
pub fn foo2() {}
|
||||||
|
|
||||||
|
/// ```{#id}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected character `#`
|
||||||
|
pub fn foo3() {}
|
||||||
|
|
||||||
|
/// ```{{
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected character `{`
|
||||||
|
pub fn foo4() {}
|
||||||
|
|
||||||
|
/// ```}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected character `}`
|
||||||
|
pub fn foo5() {}
|
||||||
|
|
||||||
|
/// ```)
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected character `)`
|
||||||
|
pub fn foo6() {}
|
||||||
|
|
||||||
|
/// ```{class=}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected `}` character after `=`
|
||||||
|
pub fn foo7() {}
|
||||||
|
|
||||||
|
/// ```(
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unclosed comment: missing `)` at the end
|
||||||
|
pub fn foo8() {}
|
||||||
|
|
||||||
|
/// ```{class=one=two}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected `=`
|
||||||
|
pub fn foo9() {}
|
||||||
|
|
||||||
|
/// ```{.one.two}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected `.` character
|
||||||
|
pub fn foo10() {}
|
||||||
|
|
||||||
|
/// ```{class=.one}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected `.` character after `=`
|
||||||
|
pub fn foo11() {}
|
||||||
|
|
||||||
|
/// ```{class=one.two}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected `.` character
|
||||||
|
pub fn foo12() {}
|
||||||
|
|
||||||
|
/// ```{(comment)}
|
||||||
|
/// main;
|
||||||
|
/// ```
|
||||||
|
//~^^^ ERROR unexpected character `(`
|
||||||
|
pub fn foo13() {}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
error: missing class name after `.`
|
error: unexpected ` ` character after `.`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```{. }
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
@ -13,53 +13,101 @@ LL | #![deny(warnings)]
|
|||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
|
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
|
||||||
|
|
||||||
error: missing class name after `class=`
|
error: unexpected ` ` character after `=`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:15:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```{class= a}
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
|
|
||||||
error: unsupported attribute `whatever=hehe`
|
error: unexpected character `#`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:21:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```{#id}
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
|
|
||||||
error: unsupported attribute `#id`
|
error: unexpected character `{`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:27:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```{{
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
|
|
||||||
error: unexpected `}` outside attribute block (`{}`)
|
error: unexpected character `}`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:33:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```}
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
|
|
||||||
error: unexpected `{` inside attribute block (`{}`)
|
error: unexpected character `)`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:39:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```)
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
|
|
||||||
error: unclosed attribute block (`{}`): missing `}` at the end
|
error: unexpected `}` character after `=`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning.rs:45:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{. class= whatever=hehe #id} } {{
|
LL | / /// ```{class=}
|
||||||
LL | | /// main;
|
LL | | /// main;
|
||||||
LL | | /// ```
|
LL | | /// ```
|
||||||
| |_______^
|
| |_______^
|
||||||
|
|
||||||
error: aborting due to 7 previous errors
|
error: unclosed comment: missing `)` at the end
|
||||||
|
--> $DIR/custom_code_classes_in_docs-warning.rs:51:1
|
||||||
|
|
|
||||||
|
LL | / /// ```(
|
||||||
|
LL | | /// main;
|
||||||
|
LL | | /// ```
|
||||||
|
| |_______^
|
||||||
|
|
||||||
|
error: unexpected `=` character
|
||||||
|
--> $DIR/custom_code_classes_in_docs-warning.rs:57:1
|
||||||
|
|
|
||||||
|
LL | / /// ```{class=one=two}
|
||||||
|
LL | | /// main;
|
||||||
|
LL | | /// ```
|
||||||
|
| |_______^
|
||||||
|
|
||||||
|
error: unexpected `.` character
|
||||||
|
--> $DIR/custom_code_classes_in_docs-warning.rs:63:1
|
||||||
|
|
|
||||||
|
LL | / /// ```{.one.two}
|
||||||
|
LL | | /// main;
|
||||||
|
LL | | /// ```
|
||||||
|
| |_______^
|
||||||
|
|
||||||
|
error: unexpected `.` character after `=`
|
||||||
|
--> $DIR/custom_code_classes_in_docs-warning.rs:69:1
|
||||||
|
|
|
||||||
|
LL | / /// ```{class=.one}
|
||||||
|
LL | | /// main;
|
||||||
|
LL | | /// ```
|
||||||
|
| |_______^
|
||||||
|
|
||||||
|
error: unexpected `.` character
|
||||||
|
--> $DIR/custom_code_classes_in_docs-warning.rs:75:1
|
||||||
|
|
|
||||||
|
LL | / /// ```{class=one.two}
|
||||||
|
LL | | /// main;
|
||||||
|
LL | | /// ```
|
||||||
|
| |_______^
|
||||||
|
|
||||||
|
error: unexpected character `(`
|
||||||
|
--> $DIR/custom_code_classes_in_docs-warning.rs:81:1
|
||||||
|
|
|
||||||
|
LL | / /// ```{(comment)}
|
||||||
|
LL | | /// main;
|
||||||
|
LL | | /// ```
|
||||||
|
| |_______^
|
||||||
|
|
||||||
|
error: aborting due to 13 previous errors
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// This test ensures that warnings are working as expected for "custom_code_classes_in_docs"
|
|
||||||
// feature.
|
|
||||||
|
|
||||||
#![feature(custom_code_classes_in_docs)]
|
|
||||||
#![deny(warnings)]
|
|
||||||
#![feature(no_core)]
|
|
||||||
#![no_core]
|
|
||||||
|
|
||||||
/// ```{class=}
|
|
||||||
/// main;
|
|
||||||
/// ```
|
|
||||||
//~^^^ ERROR missing class name after `class=`
|
|
||||||
pub fn foo() {}
|
|
@ -1,17 +0,0 @@
|
|||||||
error: missing class name after `class=`
|
|
||||||
--> $DIR/custom_code_classes_in_docs-warning2.rs:9:1
|
|
||||||
|
|
|
||||||
LL | / /// ```{class=}
|
|
||||||
LL | | /// main;
|
|
||||||
LL | | /// ```
|
|
||||||
| |_______^
|
|
||||||
|
|
|
||||||
note: the lint level is defined here
|
|
||||||
--> $DIR/custom_code_classes_in_docs-warning2.rs:5:9
|
|
||||||
|
|
|
||||||
LL | #![deny(warnings)]
|
|
||||||
| ^^^^^^^^
|
|
||||||
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
|
|
||||||
|
|
||||||
error: aborting due to previous error
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
error: unclosed quote string: missing `"` at the end
|
error: unclosed quote string `"`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{class="}
|
LL | / /// ```{class="}
|
||||||
@ -17,7 +17,7 @@ LL | #![deny(warnings)]
|
|||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
|
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
|
||||||
|
|
||||||
error: unclosed quote string: missing `"` at the end
|
error: unclosed quote string `"`
|
||||||
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1
|
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1
|
||||||
|
|
|
|
||||||
LL | / /// ```{class="}
|
LL | / /// ```{class="}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
///
|
///
|
||||||
/// Testing with multiple "unknown". Only the first should be used.
|
/// Testing with multiple "unknown". Only the first should be used.
|
||||||
///
|
///
|
||||||
/// ```whatever4{.huhu-c} whatever5
|
/// ```whatever4,{.huhu-c} whatever5
|
||||||
/// main;
|
/// main;
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bar;
|
pub struct Bar;
|
||||||
|
Loading…
Reference in New Issue
Block a user