diff --git a/cli/doctest_runner.rs b/cli/doctest_runner.rs index c55f3d923945c4..a61da71cb3fc94 100644 --- a/cli/doctest_runner.rs +++ b/cli/doctest_runner.rs @@ -1,5 +1,7 @@ use regex::Regex; -use std::path::{Path, PathBuf}; +use std::collections::HashSet; +use std::ffi::OsStr; +use std::path::PathBuf; use crate::fs as deno_fs; use crate::installer::is_remote_url; @@ -7,7 +9,7 @@ use crate::test_runner::is_supported; pub struct DocTest { // This removes repetition of imports in a file - imports: std::collections::HashSet, + imports: HashSet, // This contains codes in an @example section with their imports removed bodies: Vec, } @@ -15,30 +17,29 @@ pub struct DocTest { struct DocTestBody { caption: String, line_number: usize, - path: PathBuf, + path: String, value: String, ignore: bool, is_async: bool, } pub fn prepare_doctest( - include: Vec, - // root_path: &PathBuf, + mut include: Vec, + root_path: &PathBuf, ) -> Vec { - let include_paths: Vec<_> = - include.into_iter().filter(|n| !is_remote_url(n)).collect(); + include.retain(|n| !is_remote_url(n)); let mut prepared = vec![]; - for path in include_paths { - let p = deno_fs::normalize_path(&Path::new(&path)); + for path in include { + let p = deno_fs::normalize_path(&root_path.join(path)); if p.is_dir() { - let test_files = crate::fs::files_in_subtree(p, |p| { - let supported_files = ["ts", "tsx", "js", "jsx"]; - match p.extension().and_then(std::ffi::OsStr::to_str) { - Some(x) => supported_files.contains(&x) && !is_supported(p), - _ => false, - } + let test_files = deno_fs::files_in_subtree(p, |p| { + let valid_ext = ["ts", "tsx", "js", "jsx"]; + p.extension() + .and_then(OsStr::to_str) + .map(|ext| valid_ext.contains(&ext) && !is_supported(p)) + .unwrap_or(false) }); prepared.extend(test_files); } else { @@ -50,9 +51,8 @@ pub fn prepare_doctest( .iter() .filter_map(|dir| { // TODO(iykekings) use deno error instead - let content = std::fs::read_to_string(&dir).expect( - format!("File doesn't exist {}", dir.to_str().unwrap_or("")).as_str(), - ); + let content = std::fs::read_to_string(&dir) + .unwrap_or_else(|_| panic!("File doesn't exist {}", dir.display())); extract_jsdoc_examples(content, dir.to_owned()) }) .collect::>() @@ -62,17 +62,15 @@ fn extract_jsdoc_examples(input: String, p: PathBuf) -> Option { lazy_static! { static ref JS_DOC_PATTERN: Regex = Regex::new(r"/\*\*\s*\n([^\*]|\*[^/])*\*/").unwrap(); - // IMPORT_PATTERN doesn't match dynamic imports + // IMPORT_PATTERN doesn't match dynamic imports by design static ref IMPORT_PATTERN: Regex = Regex::new(r"import[^(].*\n").unwrap(); static ref EXAMPLE_PATTERN: Regex = Regex::new(r"@example\s*(?:<\w+>.*)*\n(?:\s*\*\s*\n*)*```").unwrap(); - static ref TICKS_OR_IMPORT_PATTERN: Regex = Regex::new(r"(?:import[^(].*)|(?:```\w*)").unwrap(); - static ref CAPTION_PATTERN: Regex = Regex::new(r"([\s\w\W]+)").unwrap(); static ref TEST_TAG_PATTERN: Regex = Regex::new(r"@example\s*(?:<\w+>.*)*\n(?:\s*\*\s*\n*)*```(\w+)").unwrap(); static ref AWAIT_PATTERN: Regex = Regex::new(r"\Wawait\s").unwrap(); } - let mut import_set = std::collections::HashSet::new(); + let mut import_set = HashSet::new(); let test_bodies = JS_DOC_PATTERN .captures_iter(&input) @@ -103,38 +101,21 @@ fn extract_jsdoc_examples(input: String, p: PathBuf) -> Option { import_set.insert(import.to_string()); }); - let caption = CAPTION_PATTERN - .captures(&example_section) - .and_then(|cap| cap.get(1).map(|m| m.as_str())) - .unwrap_or(""); - + let caption = get_caption_from_example(&example_section); let line_number = &input[0..offset].lines().count(); - - let body = TICKS_OR_IMPORT_PATTERN - .replace_all(&example_section, "\n") - .lines() - .skip(1) - .filter_map(|line| { - let res = match line.trim_start().starts_with("*") { - true => line.replacen("*", "", 1).trim_start().to_string(), - false => line.trim_start().to_string(), - }; - match res.len() { - 0 => None, - _ => Some(format!(" {}", res)), - } - }) - .collect::>() - .join("\n"); - let is_async = match AWAIT_PATTERN.find(&example_section) { - Some(_) => true, - _ => false, - }; + let code_block = get_code_from_example(&example_section); + let is_async = AWAIT_PATTERN.find(&example_section).is_some(); + + let cwd = std::env::current_dir() + .expect("expected: process has a current working directory"); + let path = p + .to_str() + .map(|x| x.replace(cwd.to_str().unwrap_or(""), "")); Some(DocTestBody { - caption: caption.to_owned(), - line_number: line_number.clone(), - path: p.clone(), - value: body, + caption, + line_number: *line_number, + path: path.unwrap_or("".to_string()), + value: code_block, ignore: test_tag == Some("ignore"), is_async, }) @@ -192,8 +173,8 @@ pub fn render_doctest_file( .map(|test| { let async_str = if test.is_async {"async "} else {""}; format!( - "Deno.test({{\n\tname: \"{} -> {} (line {})\",\n\tignore: {},\n\t{}fn() {{\n{}\n}}\n}});\n", - test.path.display(), + "Deno.test({{\n\tname: \"{} - {} (line {})\",\n\tignore: {},\n\t{}fn() {{\n{}\n}}\n}});\n", + &test.path[1..], test.caption, test.line_number, test.ignore, @@ -221,3 +202,38 @@ pub fn render_doctest_file( test_file } + +fn get_caption_from_example(ex: &str) -> String { + lazy_static! { + static ref CAPTION_PATTERN: Regex = + Regex::new(r"([\s\w\W]+)").unwrap(); + } + CAPTION_PATTERN + .captures(ex) + .and_then(|cap| cap.get(1).map(|m| m.as_str())) + .unwrap_or("") + .to_string() +} + +fn get_code_from_example(ex: &str) -> String { + lazy_static! { + static ref TICKS_OR_IMPORT_PATTERN: Regex = + Regex::new(r"(?:import[^(].*)|(?:```\w*)").unwrap(); + } + TICKS_OR_IMPORT_PATTERN + .replace_all(ex, "\n") + .lines() + .skip(1) + .filter_map(|line| { + let res = match line.trim_start().starts_with('*') { + true => line.replacen("*", "", 1).trim_start().to_string(), + false => line.trim_start().to_string(), + }; + match res.len() { + 0 => None, + _ => Some(format!(" {}", res)), + } + }) + .collect::>() + .join("\n") +} diff --git a/cli/main.rs b/cli/main.rs index 9b732a6759db37..eb6bff8f420a85 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -528,9 +528,10 @@ async fn doctest_command( filter: Option, ) -> Result<(), ErrBox> { let global_state = GlobalState::new(flags.clone())?; - let cwd = std::env::current_dir().expect("No current directory"); + let cwd = std::env::current_dir() + .expect("expected: process has a current working directory"); let include = include.unwrap_or_else(|| vec![".".to_string()]); - let doctests = doctest_runner::prepare_doctest(include); + let doctests = doctest_runner::prepare_doctest(include, &cwd); if doctests.is_empty() { println!("No matching doctest modules found");