1
Fork 0

[librustdoc] Reform lang string token splitting

Only split doctest lang strings on `,`, ` `, and `\t`. Additionally, to
preserve backwards compatibility with pandoc-style langstrings, strip a
surrounding `{}`, and remove leading `.`s from each token.

Prior to this change, doctest lang strings were split on all
non-alphanumeric characters except `-` or `_`, which limited future
extensions to doctest lang string tokens, for example using `=` for
key-value tokens.

This is a breaking change, although it is not expected to be disruptive,
because lang strings using separators other than `,` and ` ` are not
very common
This commit is contained in:
Casey Rodarmor 2020-10-26 19:08:42 -07:00
parent e9920ef774
commit 66f4883308
No known key found for this signature in database
GPG key ID: 556186B153EC6FE0
6 changed files with 84 additions and 20 deletions

View file

@ -2,7 +2,7 @@ Multiple candidate files were found for an out-of-line module.
Erroneous code example: Erroneous code example:
```ignore (multiple source files required for compile_fail) ```ignore (Multiple source files are required for compile_fail.)
// file: ambiguous_module/mod.rs // file: ambiguous_module/mod.rs
fn foo() {} fn foo() {}

View file

@ -10,7 +10,7 @@
//! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use //! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use
//! `visit_results`. The following example uses the `ResultsCursor` approach. //! `visit_results`. The following example uses the `ResultsCursor` approach.
//! //!
//! ```ignore(cross-crate-imports) //! ```ignore (cross-crate-imports)
//! use rustc_mir::dataflow::Analysis; // Makes `into_engine` available. //! use rustc_mir::dataflow::Analysis; // Makes `into_engine` available.
//! //!
//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) { //! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
@ -211,7 +211,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// default impl and the one for all `A: GenKillAnalysis` will do the right thing. /// default impl and the one for all `A: GenKillAnalysis` will do the right thing.
/// Its purpose is to enable method chaining like so: /// Its purpose is to enable method chaining like so:
/// ///
/// ```ignore(cross-crate-imports) /// ```ignore (cross-crate-imports)
/// let results = MyAnalysis::new(tcx, body) /// let results = MyAnalysis::new(tcx, body)
/// .into_engine(tcx, body, def_id) /// .into_engine(tcx, body, def_id)
/// .iterate_to_fixpoint() /// .iterate_to_fixpoint()

View file

@ -336,7 +336,7 @@ impl<T> Option<T> {
/// assert_eq!(x.expect("fruits are healthy"), "value"); /// assert_eq!(x.expect("fruits are healthy"), "value");
/// ``` /// ```
/// ///
/// ```{.should_panic} /// ```should_panic
/// let x: Option<&str> = None; /// let x: Option<&str> = None;
/// x.expect("fruits are healthy"); // panics with `fruits are healthy` /// x.expect("fruits are healthy"); // panics with `fruits are healthy`
/// ``` /// ```
@ -372,7 +372,7 @@ impl<T> Option<T> {
/// assert_eq!(x.unwrap(), "air"); /// assert_eq!(x.unwrap(), "air");
/// ``` /// ```
/// ///
/// ```{.should_panic} /// ```should_panic
/// let x: Option<&str> = None; /// let x: Option<&str> = None;
/// assert_eq!(x.unwrap(), "air"); // fails /// assert_eq!(x.unwrap(), "air"); // fails
/// ``` /// ```
@ -1114,7 +1114,7 @@ impl<T: fmt::Debug> Option<T> {
/// } /// }
/// ``` /// ```
/// ///
/// ```{.should_panic} /// ```should_panic
/// #![feature(option_expect_none)] /// #![feature(option_expect_none)]
/// ///
/// use std::collections::HashMap; /// use std::collections::HashMap;
@ -1156,7 +1156,7 @@ impl<T: fmt::Debug> Option<T> {
/// } /// }
/// ``` /// ```
/// ///
/// ```{.should_panic} /// ```should_panic
/// #![feature(option_unwrap_none)] /// #![feature(option_unwrap_none)]
/// ///
/// use std::collections::HashMap; /// use std::collections::HashMap;

View file

@ -112,7 +112,7 @@
//! assert success with [`expect`]. This will panic if the //! assert success with [`expect`]. This will panic if the
//! write fails, providing a marginally useful message indicating why: //! write fails, providing a marginally useful message indicating why:
//! //!
//! ```{.no_run} //! ```no_run
//! use std::fs::File; //! use std::fs::File;
//! use std::io::prelude::*; //! use std::io::prelude::*;
//! //!
@ -122,7 +122,7 @@
//! //!
//! You might also simply assert success: //! You might also simply assert success:
//! //!
//! ```{.no_run} //! ```no_run
//! # use std::fs::File; //! # use std::fs::File;
//! # use std::io::prelude::*; //! # use std::io::prelude::*;
//! # let mut file = File::create("valuable_data.txt").unwrap(); //! # let mut file = File::create("valuable_data.txt").unwrap();
@ -984,7 +984,7 @@ impl<T, E: fmt::Debug> Result<T, E> {
/// ///
/// Basic usage: /// Basic usage:
/// ///
/// ```{.should_panic} /// ```should_panic
/// let x: Result<u32, &str> = Err("emergency failure"); /// let x: Result<u32, &str> = Err("emergency failure");
/// x.expect("Testing expect"); // panics with `Testing expect: emergency failure` /// x.expect("Testing expect"); // panics with `Testing expect: emergency failure`
/// ``` /// ```
@ -1024,7 +1024,7 @@ impl<T, E: fmt::Debug> Result<T, E> {
/// assert_eq!(x.unwrap(), 2); /// assert_eq!(x.unwrap(), 2);
/// ``` /// ```
/// ///
/// ```{.should_panic} /// ```should_panic
/// let x: Result<u32, &str> = Err("emergency failure"); /// let x: Result<u32, &str> = Err("emergency failure");
/// x.unwrap(); // panics with `emergency failure` /// x.unwrap(); // panics with `emergency failure`
/// ``` /// ```
@ -1052,7 +1052,7 @@ impl<T: fmt::Debug, E> Result<T, E> {
/// ///
/// Basic usage: /// Basic usage:
/// ///
/// ```{.should_panic} /// ```should_panic
/// let x: Result<u32, &str> = Ok(10); /// let x: Result<u32, &str> = Ok(10);
/// x.expect_err("Testing expect_err"); // panics with `Testing expect_err: 10` /// x.expect_err("Testing expect_err"); // panics with `Testing expect_err: 10`
/// ``` /// ```
@ -1075,7 +1075,7 @@ impl<T: fmt::Debug, E> Result<T, E> {
/// ///
/// # Examples /// # Examples
/// ///
/// ```{.should_panic} /// ```should_panic
/// let x: Result<u32, &str> = Ok(2); /// let x: Result<u32, &str> = Ok(2);
/// x.unwrap_err(); // panics with `2` /// x.unwrap_err(); // panics with `2`
/// ``` /// ```

View file

@ -779,6 +779,31 @@ impl LangString {
Self::parse(string, allow_error_code_check, enable_per_target_ignores, None) Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
} }
fn tokens(string: &str) -> impl Iterator<Item = &str> {
// Pandoc, which Rust once used for generating documentation,
// expects lang strings to be surrounded by `{}` and for each token
// to be proceeded by a `.`. Since some of these lang strings are still
// loose in the wild, we strip a pair of surrounding `{}` from the lang
// string and a leading `.` from each token.
let string = string.trim();
let first = string.chars().next();
let last = string.chars().last();
let string = if first == Some('{') && last == Some('}') {
&string[1..string.len() - 1]
} else {
string
};
string
.split(|c| c == ',' || c == ' ' || c == '\t')
.map(str::trim)
.map(|token| if token.chars().next() == Some('.') { &token[1..] } else { token })
.filter(|token| !token.is_empty())
}
fn parse( fn parse(
string: &str, string: &str,
allow_error_code_check: ErrorCodes, allow_error_code_check: ErrorCodes,
@ -792,11 +817,11 @@ impl LangString {
let mut ignores = vec![]; let mut ignores = vec![];
data.original = string.to_owned(); data.original = string.to_owned();
let tokens = string.split(|c: char| !(c == '_' || c == '-' || c.is_alphanumeric()));
let tokens = Self::tokens(string).collect::<Vec<&str>>();
for token in tokens { for token in tokens {
match token.trim() { match token {
"" => {}
"should_panic" => { "should_panic" => {
data.should_panic = true; data.should_panic = true;
seen_rust_tags = !seen_other_tags; seen_rust_tags = !seen_other_tags;
@ -893,6 +918,7 @@ impl LangString {
_ => seen_other_tags = true, _ => seen_other_tags = true,
} }
} }
// ignore-foo overrides ignore // ignore-foo overrides ignore
if !ignores.is_empty() { if !ignores.is_empty() {
data.ignore = Ignore::Some(ignores); data.ignore = Ignore::Some(ignores);

View file

@ -58,6 +58,9 @@ 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 { original: ".rust".into(), ..Default::default() });
t(LangString { original: "{rust}".into(), ..Default::default() });
t(LangString { original: "{.rust}".into(), ..Default::default() });
t(LangString { original: "sh".into(), rust: false, ..Default::default() }); t(LangString { original: "sh".into(), rust: false, ..Default::default() });
t(LangString { original: "ignore".into(), ignore: Ignore::All, ..Default::default() }); t(LangString { original: "ignore".into(), ignore: Ignore::All, ..Default::default() });
t(LangString { t(LangString {
@ -75,16 +78,16 @@ fn test_lang_string_parse() {
..Default::default() ..Default::default()
}); });
t(LangString { original: "allow_fail".into(), allow_fail: true, ..Default::default() }); t(LangString { original: "allow_fail".into(), allow_fail: true, ..Default::default() });
t(LangString { original: "{.no_run .example}".into(), no_run: true, ..Default::default() }); t(LangString { original: "no_run,example".into(), no_run: true, ..Default::default() });
t(LangString { t(LangString {
original: "{.sh .should_panic}".into(), original: "sh,should_panic".into(),
should_panic: true, should_panic: true,
rust: false, rust: false,
..Default::default() ..Default::default()
}); });
t(LangString { original: "{.example .rust}".into(), ..Default::default() }); t(LangString { original: "example,rust".into(), ..Default::default() });
t(LangString { t(LangString {
original: "{.test_harness .rust}".into(), original: "test_harness,.rust".into(),
test_harness: true, test_harness: true,
..Default::default() ..Default::default()
}); });
@ -100,6 +103,18 @@ fn test_lang_string_parse() {
rust: false, rust: false,
..Default::default() ..Default::default()
}); });
t(LangString {
original: "text,no_run, ".into(),
no_run: true,
rust: false,
..Default::default()
});
t(LangString {
original: "text,no_run,".into(),
no_run: true,
rust: false,
..Default::default()
});
t(LangString { t(LangString {
original: "edition2015".into(), original: "edition2015".into(),
edition: Some(Edition::Edition2015), edition: Some(Edition::Edition2015),
@ -112,6 +127,29 @@ fn test_lang_string_parse() {
}); });
} }
#[test]
fn test_lang_string_tokenizer() {
fn case(lang_string: &str, want: &[&str]) {
let have = LangString::tokens(lang_string).collect::<Vec<&str>>();
assert_eq!(have, want, "Unexpected lang string split for `{}`", lang_string);
}
case("", &[]);
case("foo", &["foo"]);
case("foo,bar", &["foo", "bar"]);
case(".foo,.bar", &["foo", "bar"]);
case("{.foo,.bar}", &["foo", "bar"]);
case(" {.foo,.bar} ", &["foo", "bar"]);
case("foo bar", &["foo", "bar"]);
case("foo\tbar", &["foo", "bar"]);
case("foo\t, bar", &["foo", "bar"]);
case(" foo , bar ", &["foo", "bar"]);
case(",,foo,,bar,,", &["foo", "bar"]);
case("foo=bar", &["foo=bar"]);
case("a-b-c", &["a-b-c"]);
case("a_b_c", &["a_b_c"]);
}
#[test] #[test]
fn test_header() { fn test_header() {
fn t(input: &str, expect: &str) { fn t(input: &str, expect: &str) {