From d79a10f411743947817d7748a9e89d33fc51105e Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Mon, 26 Aug 2024 16:32:59 +0900 Subject: [PATCH 01/31] feat(cli): make `deno test --doc` run doc tests --- cli/lsp/testing/execution.rs | 13 +- cli/tools/test/extract.rs | 990 +++++++++++++++++++++++++++++++++++ cli/tools/test/mod.rs | 350 ++++--------- 3 files changed, 1082 insertions(+), 271 deletions(-) create mode 100644 cli/tools/test/extract.rs diff --git a/cli/lsp/testing/execution.rs b/cli/lsp/testing/execution.rs index aec91b3e7d2752..42fd09b09d2739 100644 --- a/cli/lsp/testing/execution.rs +++ b/cli/lsp/testing/execution.rs @@ -227,16 +227,9 @@ impl TestRun { let permissions = Permissions::from_options(&cli_options.permissions_options()?)?; let main_graph_container = factory.main_module_graph_container().await?; - test::check_specifiers( - factory.file_fetcher()?, - main_graph_container, - self - .queue - .iter() - .map(|s| (s.clone(), test::TestMode::Executable)) - .collect(), - ) - .await?; + main_graph_container + .check_specifiers(&self.queue.iter().cloned().collect::>()) + .await?; let (concurrent_jobs, fail_fast) = if let DenoSubcommand::Test(test_flags) = cli_options.sub_command() { diff --git a/cli/tools/test/extract.rs b/cli/tools/test/extract.rs new file mode 100644 index 00000000000000..913a3c380fd323 --- /dev/null +++ b/cli/tools/test/extract.rs @@ -0,0 +1,990 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use deno_ast::swc::ast; +use deno_ast::swc::atoms::Atom; +use deno_ast::swc::common::comments::CommentKind; +use deno_ast::swc::common::DUMMY_SP; +use deno_ast::swc::visit::as_folder; +use deno_ast::swc::visit::FoldWith as _; +use deno_ast::swc::visit::Visit; +use deno_ast::swc::visit::VisitMut; +use deno_ast::swc::visit::VisitWith as _; +use deno_ast::MediaType; +use deno_ast::SourceRangedForSpanned as _; +use deno_core::error::AnyError; +use deno_core::ModuleSpecifier; +use regex::Regex; +use std::fmt::Write as _; +use std::sync::Arc; + +use crate::file_fetcher::File; +use crate::util::path::mapped_specifier_for_tsc; + +/// Extracts doc tests from a given file, transforms them into pseudo test +/// files by wrapping the content of the doc tests in a `Deno.test` call, and +/// returns a list of the pseudo test files. +pub(super) fn extract_doc_tests(file: File) -> Result, AnyError> { + let file = file.into_text_decoded()?; + + let exports = match deno_ast::parse_program(deno_ast::ParseParams { + specifier: file.specifier.clone(), + text: file.source.clone(), + media_type: file.media_type, + capture_tokens: false, + scope_analysis: false, + maybe_syntax: None, + }) { + Ok(parsed) => { + let mut c = ExportCollector::default(); + c.visit_program(parsed.program_ref()); + c + } + Err(_) => ExportCollector::default(), + }; + + let extracted_files = if file.media_type == MediaType::Unknown { + extract_files_from_fenced_blocks( + &file.specifier, + &file.source, + file.media_type, + )? + } else { + extract_files_from_source_comments( + &file.specifier, + file.source.clone(), + file.media_type, + )? + }; + + Ok( + extracted_files + .into_iter() + .map(|extracted_file| { + generate_pseudo_test_file(extracted_file, &file.specifier, &exports) + }) + .collect::>()?, + ) +} + +fn extract_files_from_fenced_blocks( + specifier: &ModuleSpecifier, + source: &str, + media_type: MediaType, +) -> Result, AnyError> { + // The pattern matches code blocks as well as anything in HTML comment syntax, + // but it stores the latter without any capturing groups. This way, a simple + // check can be done to see if a block is inside a comment (and skip typechecking) + // or not by checking for the presence of capturing groups in the matches. + let blocks_regex = + lazy_regex::regex!(r"(?s)|```([^\r\n]*)\r?\n([\S\s]*?)```"); + let lines_regex = lazy_regex::regex!(r"(?:\# ?)?(.*)"); + + extract_files_from_regex_blocks( + specifier, + source, + media_type, + /* file line index */ 0, + blocks_regex, + lines_regex, + ) +} + +fn extract_files_from_source_comments( + specifier: &ModuleSpecifier, + source: Arc, + media_type: MediaType, +) -> Result, AnyError> { + let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { + specifier: specifier.clone(), + text: source, + media_type, + capture_tokens: false, + maybe_syntax: None, + scope_analysis: false, + })?; + let comments = parsed_source.comments().get_vec(); + let blocks_regex = lazy_regex::regex!(r"```([^\r\n]*)\r?\n([\S\s]*?)```"); + let lines_regex = lazy_regex::regex!(r"(?:\* ?)(?:\# ?)?(.*)"); + + let files = comments + .iter() + .filter(|comment| { + if comment.kind != CommentKind::Block || !comment.text.starts_with('*') { + return false; + } + + true + }) + .flat_map(|comment| { + extract_files_from_regex_blocks( + specifier, + &comment.text, + media_type, + parsed_source.text_info_lazy().line_index(comment.start()), + blocks_regex, + lines_regex, + ) + }) + .flatten() + .collect(); + + Ok(files) +} + +fn extract_files_from_regex_blocks( + specifier: &ModuleSpecifier, + source: &str, + media_type: MediaType, + file_line_index: usize, + blocks_regex: &Regex, + lines_regex: &Regex, +) -> Result, AnyError> { + let files = blocks_regex + .captures_iter(source) + .filter_map(|block| { + block.get(1)?; + + let maybe_attributes: Option> = block + .get(1) + .map(|attributes| attributes.as_str().split(' ').collect()); + + let file_media_type = if let Some(attributes) = maybe_attributes { + if attributes.contains(&"ignore") { + return None; + } + + match attributes.first() { + Some(&"js") => MediaType::JavaScript, + Some(&"javascript") => MediaType::JavaScript, + Some(&"mjs") => MediaType::Mjs, + Some(&"cjs") => MediaType::Cjs, + Some(&"jsx") => MediaType::Jsx, + Some(&"ts") => MediaType::TypeScript, + Some(&"typescript") => MediaType::TypeScript, + Some(&"mts") => MediaType::Mts, + Some(&"cts") => MediaType::Cts, + Some(&"tsx") => MediaType::Tsx, + _ => MediaType::Unknown, + } + } else { + media_type + }; + + if file_media_type == MediaType::Unknown { + return None; + } + + let line_offset = source[0..block.get(0).unwrap().start()] + .chars() + .filter(|c| *c == '\n') + .count(); + + let line_count = block.get(0).unwrap().as_str().split('\n').count(); + + let body = block.get(2).unwrap(); + let text = body.as_str(); + + // TODO(caspervonb) generate an inline source map + let mut file_source = String::new(); + for line in lines_regex.captures_iter(text) { + let text = line.get(1).unwrap(); + writeln!(file_source, "{}", text.as_str()).unwrap(); + } + + let file_specifier = ModuleSpecifier::parse(&format!( + "{}${}-{}", + specifier, + file_line_index + line_offset + 1, + file_line_index + line_offset + line_count + 1, + )) + .unwrap(); + let file_specifier = + mapped_specifier_for_tsc(&file_specifier, file_media_type) + .map(|s| ModuleSpecifier::parse(&s).unwrap()) + .unwrap_or(file_specifier); + + Some(File { + specifier: file_specifier, + maybe_headers: None, + source: file_source.into_bytes().into(), + }) + }) + .collect(); + + Ok(files) +} + +#[derive(Default)] +struct ExportCollector { + named_exports: Vec, + default_export: Option, +} + +impl ExportCollector { + fn to_import_specifiers(&self) -> Vec { + let mut import_specifiers = vec![]; + if let Some(default_export) = &self.default_export { + import_specifiers.push(ast::ImportSpecifier::Default( + ast::ImportDefaultSpecifier { + span: DUMMY_SP, + local: ast::Ident { + span: DUMMY_SP, + ctxt: Default::default(), + sym: default_export.clone(), + optional: false, + }, + }, + )); + } + for named_export in &self.named_exports { + import_specifiers.push(ast::ImportSpecifier::Named( + ast::ImportNamedSpecifier { + span: DUMMY_SP, + local: ast::Ident { + span: DUMMY_SP, + ctxt: Default::default(), + sym: named_export.clone(), + optional: false, + }, + imported: None, + is_type_only: false, + }, + )); + } + import_specifiers + } +} + +impl Visit for ExportCollector { + fn visit_export_decl(&mut self, export_decl: &ast::ExportDecl) { + match &export_decl.decl { + ast::Decl::Class(class) => { + self.named_exports.push(class.ident.sym.clone()); + } + ast::Decl::Fn(func) => { + self.named_exports.push(func.ident.sym.clone()); + } + ast::Decl::Var(var) => { + for var_decl in &var.decls { + let atoms = extract_sym_from_pat(&var_decl.name); + self.named_exports.extend(atoms); + } + } + ast::Decl::TsEnum(ts_enum) => { + self.named_exports.push(ts_enum.id.sym.clone()); + } + ast::Decl::TsModule(ts_module) => match &ts_module.id { + ast::TsModuleName::Ident(ident) => { + self.named_exports.push(ident.sym.clone()); + } + ast::TsModuleName::Str(s) => { + self.named_exports.push(s.value.clone()); + } + }, + ast::Decl::TsTypeAlias(ts_type_alias) => { + self.named_exports.push(ts_type_alias.id.sym.clone()); + } + ast::Decl::TsInterface(ts_interface) => { + self.named_exports.push(ts_interface.id.sym.clone()); + } + ast::Decl::Using(_) => {} + } + } + + fn visit_export_default_decl( + &mut self, + export_default_decl: &ast::ExportDefaultDecl, + ) { + match &export_default_decl.decl { + ast::DefaultDecl::Class(class) => { + if let Some(ident) = &class.ident { + self.default_export = Some(ident.sym.clone()); + } + } + ast::DefaultDecl::Fn(func) => { + if let Some(ident) = &func.ident { + self.default_export = Some(ident.sym.clone()); + } + } + ast::DefaultDecl::TsInterfaceDecl(_) => {} + } + } + + fn visit_export_named_specifier( + &mut self, + export_named_specifier: &ast::ExportNamedSpecifier, + ) { + fn get_atom(export_name: &ast::ModuleExportName) -> Atom { + match export_name { + ast::ModuleExportName::Ident(ident) => ident.sym.clone(), + ast::ModuleExportName::Str(s) => s.value.clone(), + } + } + + match &export_named_specifier.exported { + Some(exported) => { + self.named_exports.push(get_atom(exported)); + } + None => { + self + .named_exports + .push(get_atom(&export_named_specifier.orig)); + } + } + } + + fn visit_named_export(&mut self, named_export: &ast::NamedExport) { + // ExportCollector does not handle re-exports + if named_export.src.is_some() { + return; + } + + named_export.visit_children_with(self); + } +} + +fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { + fn rec(pat: &ast::Pat, atoms: &mut Vec) { + match pat { + ast::Pat::Ident(binding_ident) => { + atoms.push(binding_ident.sym.clone()); + } + ast::Pat::Array(array_pat) => { + for elem in array_pat.elems.iter().cloned().filter_map(|e| e) { + rec(&elem, atoms); + } + } + ast::Pat::Rest(rest_pat) => { + rec(&*rest_pat.arg, atoms); + } + ast::Pat::Object(object_pat) => { + for prop in &object_pat.props { + match prop { + ast::ObjectPatProp::Assign(assign_pat_prop) => { + atoms.push(assign_pat_prop.key.sym.clone()); + } + ast::ObjectPatProp::KeyValue(key_value_pat_prop) => { + rec(&key_value_pat_prop.value, atoms); + } + ast::ObjectPatProp::Rest(rest_pat) => { + rec(&*rest_pat.arg, atoms); + } + } + } + } + ast::Pat::Assign(assign_pat) => { + rec(&*assign_pat.left, atoms); + } + ast::Pat::Invalid(_) | ast::Pat::Expr(_) => {} + } + } + + let mut atoms = vec![]; + rec(pat, &mut atoms); + atoms +} + +/// Generates a "pseudo" test file from a given file by applying the following +/// transformations: +/// +/// 1. Injects `import` statements for expoted items from the base file +/// 2. Wraps the content of the file in a `Deno.test` call +/// +/// For example, given a file that looks like: +/// ```ts +/// import { assertEquals } from "@std/assert/equal"; +/// +/// assertEquals(increment(1), 2); +/// ``` +/// +/// and the base file (from which the above snippet was extracted): +/// +/// ```ts +/// export function increment(n: number): number { +/// return n + 1; +/// } +/// +/// export const SOME_CONST = "HELLO"; +/// ``` +/// +/// The generated pseudo test file would look like: +/// +/// ```ts +/// import { assertEquals } from "@std/assert/equal"; +/// import { increment, SOME_CONST } from "./base.ts"; +/// +/// Deno.test("./base.ts$1-3.ts", async () => { +/// assertEquals(increment(1), 2); +/// }); +/// ``` +fn generate_pseudo_test_file( + file: File, + base_file_specifier: &ModuleSpecifier, + exports: &ExportCollector, +) -> Result { + let file = file.into_text_decoded()?; + + let parsed = deno_ast::parse_program(deno_ast::ParseParams { + specifier: file.specifier.clone(), + text: file.source, + media_type: file.media_type, + capture_tokens: false, + scope_analysis: false, + maybe_syntax: None, + })?; + + let transformed = + parsed + .program_ref() + .clone() + .fold_with(&mut as_folder(Transform { + specifier: &file.specifier, + base_file_specifier, + exports, + })); + + Ok(File { + specifier: file.specifier, + maybe_headers: None, + source: deno_ast::swc::codegen::to_code(&transformed) + .into_bytes() + .into(), + }) +} + +struct Transform<'a> { + specifier: &'a ModuleSpecifier, + base_file_specifier: &'a ModuleSpecifier, + exports: &'a ExportCollector, +} + +impl<'a> VisitMut for Transform<'a> { + fn visit_mut_program(&mut self, node: &mut ast::Program) { + let new_module_items = match node { + ast::Program::Module(module) => { + let mut module_decls = vec![]; + let mut stmts = vec![]; + + for item in &module.body { + match item { + ast::ModuleItem::ModuleDecl(decl) => { + module_decls.push(decl.clone()); + } + ast::ModuleItem::Stmt(stmt) => { + stmts.push(stmt.clone()); + } + } + } + + let mut transformed_items = vec![]; + transformed_items + .extend(module_decls.into_iter().map(ast::ModuleItem::ModuleDecl)); + let import_specifiers = self.exports.to_import_specifiers(); + if !import_specifiers.is_empty() { + transformed_items.push(ast::ModuleItem::ModuleDecl( + ast::ModuleDecl::Import(ast::ImportDecl { + span: DUMMY_SP, + specifiers: import_specifiers, + src: Box::new(ast::Str { + span: DUMMY_SP, + value: self.base_file_specifier.to_string().into(), + raw: None, + }), + type_only: false, + with: None, + phase: ast::ImportPhase::Evaluation, + }), + )); + } + transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( + stmts, + self.specifier.to_string().into(), + ))); + + transformed_items + } + ast::Program::Script(script) => { + let mut transformed_items = vec![]; + + let import_specifiers = self.exports.to_import_specifiers(); + if !import_specifiers.is_empty() { + transformed_items.push(ast::ModuleItem::ModuleDecl( + ast::ModuleDecl::Import(ast::ImportDecl { + span: DUMMY_SP, + specifiers: self.exports.to_import_specifiers(), + src: Box::new(ast::Str { + span: DUMMY_SP, + value: self.base_file_specifier.to_string().into(), + raw: None, + }), + type_only: false, + with: None, + phase: ast::ImportPhase::Evaluation, + }), + )); + } + + transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( + script.body.clone(), + self.specifier.to_string().into(), + ))); + + transformed_items + } + }; + + *node = ast::Program::Module(ast::Module { + span: DUMMY_SP, + body: new_module_items, + shebang: None, + }); + } +} + +fn wrap_in_deno_test(stmts: Vec, test_name: Atom) -> ast::Stmt { + ast::Stmt::Expr(ast::ExprStmt { + span: DUMMY_SP, + expr: Box::new(ast::Expr::Call(ast::CallExpr { + span: DUMMY_SP, + callee: ast::Callee::Expr(Box::new(ast::Expr::Member(ast::MemberExpr { + span: DUMMY_SP, + obj: Box::new(ast::Expr::Ident(ast::Ident { + span: DUMMY_SP, + sym: "Deno".into(), + optional: false, + ..Default::default() + })), + prop: ast::MemberProp::Ident(ast::IdentName { + span: DUMMY_SP, + sym: "test".into(), + }), + }))), + args: vec![ + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str { + span: DUMMY_SP, + value: test_name, + raw: None, + }))), + }, + ast::ExprOrSpread { + spread: None, + expr: Box::new(ast::Expr::Arrow(ast::ArrowExpr { + span: DUMMY_SP, + params: vec![], + body: Box::new(ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { + span: DUMMY_SP, + stmts, + ..Default::default() + })), + is_async: true, + is_generator: false, + type_params: None, + return_type: None, + ..Default::default() + })), + }, + ], + type_args: None, + ..Default::default() + })), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::file_fetcher::TextDecodedFile; + use deno_ast::swc::atoms::Atom; + use pretty_assertions::assert_eq; + + #[test] + fn test_extract_doc_tests() { + struct Input { + source: &'static str, + specifier: &'static str, + } + struct Expected { + source: &'static str, + specifier: &'static str, + media_type: MediaType, + } + struct Test { + input: Input, + expected: Vec, + } + + let tests = [ + Test { + input: Input { + source: r#""#, + specifier: "file:///main.ts", + }, + expected: vec![], + }, + Test { + input: Input { + source: r#" +/** + * ```ts + * import { assertEquals } from "@std/assert/equal"; + * + * assertEquals(add(1, 2), 3); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { assertEquals } from "@std/assert/equal"; +import { add } from "file:///main.ts"; +Deno.test("file:///main.ts$3-8.ts", async ()=>{ + assertEquals(add(1, 2), 3); +}); +"#, + specifier: "file:///main.ts$3-8.ts", + media_type: MediaType::TypeScript, + }], + }, + Test { + input: Input { + source: r#" +/** + * ```ts + * foo(); + * ``` + */ +export function foo() {} + +export default class Bar {} +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import Bar, { foo } from "file:///main.ts"; +Deno.test("file:///main.ts$3-6.ts", async ()=>{ + foo(); +}); +"#, + specifier: "file:///main.ts$3-6.ts", + media_type: MediaType::TypeScript, + }], + }, + Test { + input: Input { + source: r#" +/** + * ```ts + * const input = { a: 42 } satisfies Args; + * foo(input); + * ``` + */ +export function foo(args: Args) {} + +export type Args = { a: number }; +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { foo, Args } from "file:///main.ts"; +Deno.test("file:///main.ts$3-7.ts", async ()=>{ + const input = { + a: 42 + } satisfies Args; + foo(input); +}); +"#, + specifier: "file:///main.ts$3-7.ts", + media_type: MediaType::TypeScript, + }], + }, + Test { + input: Input { + source: r#" +/** + * This is a module-level doc. + * + * ```ts + * foo(); + * ``` + * + * @module doc + */ +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"Deno.test("file:///main.ts$5-8.ts", async ()=>{ + foo(); +}); +"#, + specifier: "file:///main.ts$5-8.ts", + media_type: MediaType::TypeScript, + }], + }, + Test { + input: Input { + source: r#" +/** + * This is a module-level doc. + * + * ```js + * const cls = new MyClass(); + * ``` + * + * @module doc + */ + +/** + * ```ts + * foo(); + * ``` + */ +export function foo() {} + +export default class MyClass {} + +export * from "./other.ts"; +"#, + specifier: "file:///main.ts", + }, + expected: vec![ + Expected { + source: r#"import MyClass, { foo } from "file:///main.ts"; +Deno.test("file:///main.ts$5-8.js", async ()=>{ + const cls = new MyClass(); +}); +"#, + specifier: "file:///main.ts$5-8.js", + media_type: MediaType::JavaScript, + }, + Expected { + source: r#"import MyClass, { foo } from "file:///main.ts"; +Deno.test("file:///main.ts$13-16.ts", async ()=>{ + foo(); +}); +"#, + specifier: "file:///main.ts$13-16.ts", + media_type: MediaType::TypeScript, + }, + ], + }, + Test { + input: Input { + source: r#" +# Header + +This is a *markdown*. + +```js +import { assertEquals } from "@std/assert/equal"; +import { add } from "jsr:@deno/non-existent"; + +assertEquals(add(1, 2), 3); +``` +"#, + specifier: "file:///README.md", + }, + expected: vec![Expected { + source: r#"import { assertEquals } from "@std/assert/equal"; +import { add } from "jsr:@deno/non-existent"; +Deno.test("file:///README.md$6-12.js", async ()=>{ + assertEquals(add(1, 2), 3); +}); +"#, + specifier: "file:///README.md$6-12.js", + media_type: MediaType::JavaScript, + }], + }, + ]; + + for test in tests { + let file = File { + specifier: ModuleSpecifier::parse(test.input.specifier).unwrap(), + maybe_headers: None, + source: test.input.source.as_bytes().into(), + }; + let got_decoded = extract_doc_tests(file) + .unwrap() + .into_iter() + .map(|f| f.into_text_decoded().unwrap()) + .collect::>(); + let expected = test + .expected + .iter() + .map(|e| TextDecodedFile { + specifier: ModuleSpecifier::parse(e.specifier).unwrap(), + media_type: e.media_type, + source: e.source.into(), + }) + .collect::>(); + assert_eq!(got_decoded, expected); + } + } + + #[test] + fn test_export_collector() { + fn helper(input: &'static str) -> ExportCollector { + let mut collector = ExportCollector::default(); + let parsed = deno_ast::parse_module(deno_ast::ParseParams { + specifier: deno_ast::ModuleSpecifier::parse("file:///main.ts").unwrap(), + text: input.into(), + media_type: deno_ast::MediaType::TypeScript, + capture_tokens: false, + scope_analysis: false, + maybe_syntax: None, + }) + .unwrap(); + + collector.visit_program(parsed.program_ref()); + collector + } + + struct Test { + input: &'static str, + named_expected: Vec, + default_expected: Option, + } + + let tests = [ + Test { + input: r#"export const foo = 42;"#, + named_expected: vec!["foo".into()], + default_expected: None, + }, + Test { + input: r#"export let foo = 42;"#, + named_expected: vec!["foo".into()], + default_expected: None, + }, + Test { + input: r#"export var foo = 42;"#, + named_expected: vec!["foo".into()], + default_expected: None, + }, + Test { + input: r#"export const foo = () => {};"#, + named_expected: vec!["foo".into()], + default_expected: None, + }, + Test { + input: r#"export function foo() {}"#, + named_expected: vec!["foo".into()], + default_expected: None, + }, + Test { + input: r#"export class Foo {}"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, + Test { + input: r#"export enum Foo {}"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, + Test { + input: r#"export module Foo {}"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, + Test { + input: r#"export module "foo" {}"#, + named_expected: vec!["foo".into()], + default_expected: None, + }, + Test { + input: r#"export type Foo = string;"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, + Test { + input: r#"export interface Foo {};"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, + Test { + input: r#"export let name1, name2;"#, + named_expected: vec!["name1".into(), "name2".into()], + default_expected: None, + }, + Test { + input: r#"export const name1 = 1, name2 = 2;"#, + named_expected: vec!["name1".into(), "name2".into()], + default_expected: None, + }, + Test { + input: r#"export function* generatorFunc() {}"#, + named_expected: vec!["generatorFunc".into()], + default_expected: None, + }, + Test { + input: r#"export const { name1, name2: bar } = obj;"#, + named_expected: vec!["name1".into(), "bar".into()], + default_expected: None, + }, + Test { + input: r#"export const [name1, name2] = arr;"#, + named_expected: vec!["name1".into(), "name2".into()], + default_expected: None, + }, + Test { + input: r#"export const { name1 = 42 } = arr;"#, + named_expected: vec!["name1".into()], + default_expected: None, + }, + Test { + input: r#"export default function foo() {}"#, + named_expected: vec![], + default_expected: Some("foo".into()), + }, + Test { + input: r#"export { foo, bar as barAlias };"#, + named_expected: vec!["foo".into(), "barAlias".into()], + default_expected: None, + }, + Test { + input: r#" +export default class Foo {} + +export let value1 = 42; + +const value2 = "Hello"; + +const value3 = "World"; + +export { value2 }; +"#, + named_expected: vec!["value1".into(), "value2".into()], + default_expected: Some("Foo".into()), + }, + // The collector deliberately does not handle re-exports, because from + // doc reader's perspective, an example code would become hard to follow + // if it uses re-exported items (as opposed to normal, non-re-exported + // items that would look verbose if an example code explicitly imports + // them). + Test { + input: r#" +export * from "./module1.ts"; +export * as name1 from "./module2.ts"; +export { name2, name3 as N3 } from "./module3.js"; +export { default } from "./module4.ts"; +export { default as myDefault } from "./module5.ts"; +"#, + named_expected: vec![], + default_expected: None, + }, + ]; + + for test in tests { + let got = helper(test.input); + assert_eq!(got.named_exports, test.named_expected); + assert_eq!(got.default_export, test.default_expected); + } + } +} + +// TODO(magurotuna): add tests for the transformer diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 0dc2133502f965..05184bf770733a 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -7,23 +7,18 @@ use crate::args::TestReporterConfig; use crate::colors; use crate::display; use crate::factory::CliFactory; -use crate::file_fetcher::File; use crate::file_fetcher::FileFetcher; -use crate::graph_container::MainModuleGraphContainer; use crate::graph_util::has_graph_root_local_dependent_changed; use crate::ops; use crate::util::file_watcher; use crate::util::fs::collect_specifiers; use crate::util::path::get_extension; use crate::util::path::is_script_ext; -use crate::util::path::mapped_specifier_for_tsc; use crate::util::path::matches_pattern_or_exact_path; use crate::worker::CliMainWorkerFactory; use crate::worker::CoverageCollector; -use deno_ast::swc::common::comments::CommentKind; use deno_ast::MediaType; -use deno_ast::SourceRangedForSpanned; use deno_config::glob::FilePatterns; use deno_config::glob::WalkEntry; use deno_core::anyhow; @@ -89,6 +84,7 @@ use std::time::Instant; use tokio::signal; mod channel; +mod extract; pub mod fmt; pub mod reporters; @@ -150,6 +146,20 @@ pub enum TestMode { Both, } +impl TestMode { + /// Returns `true` if the test mode indicates that code snippet extraction is + /// needed. + fn needs_test_extraction(&self) -> bool { + matches!(self, Self::Documentation | Self::Both) + } + + /// Returns `true` if the test mode indicates that the test should be + /// type-checked and run. + fn needs_test_run(&self) -> bool { + matches!(self, Self::Executable | Self::Both) + } +} + #[derive(Clone, Debug, Default)] pub struct TestFilter { pub substring: Option, @@ -1173,234 +1183,6 @@ async fn wait_for_activity_to_stabilize( }) } -fn extract_files_from_regex_blocks( - specifier: &ModuleSpecifier, - source: &str, - media_type: MediaType, - file_line_index: usize, - blocks_regex: &Regex, - lines_regex: &Regex, -) -> Result, AnyError> { - let files = blocks_regex - .captures_iter(source) - .filter_map(|block| { - block.get(1)?; - - let maybe_attributes: Option> = block - .get(1) - .map(|attributes| attributes.as_str().split(' ').collect()); - - let file_media_type = if let Some(attributes) = maybe_attributes { - if attributes.contains(&"ignore") { - return None; - } - - match attributes.first() { - Some(&"js") => MediaType::JavaScript, - Some(&"javascript") => MediaType::JavaScript, - Some(&"mjs") => MediaType::Mjs, - Some(&"cjs") => MediaType::Cjs, - Some(&"jsx") => MediaType::Jsx, - Some(&"ts") => MediaType::TypeScript, - Some(&"typescript") => MediaType::TypeScript, - Some(&"mts") => MediaType::Mts, - Some(&"cts") => MediaType::Cts, - Some(&"tsx") => MediaType::Tsx, - _ => MediaType::Unknown, - } - } else { - media_type - }; - - if file_media_type == MediaType::Unknown { - return None; - } - - let line_offset = source[0..block.get(0).unwrap().start()] - .chars() - .filter(|c| *c == '\n') - .count(); - - let line_count = block.get(0).unwrap().as_str().split('\n').count(); - - let body = block.get(2).unwrap(); - let text = body.as_str(); - - // TODO(caspervonb) generate an inline source map - let mut file_source = String::new(); - for line in lines_regex.captures_iter(text) { - let text = line.get(1).unwrap(); - writeln!(file_source, "{}", text.as_str()).unwrap(); - } - - let file_specifier = ModuleSpecifier::parse(&format!( - "{}${}-{}", - specifier, - file_line_index + line_offset + 1, - file_line_index + line_offset + line_count + 1, - )) - .unwrap(); - let file_specifier = - mapped_specifier_for_tsc(&file_specifier, file_media_type) - .map(|s| ModuleSpecifier::parse(&s).unwrap()) - .unwrap_or(file_specifier); - - Some(File { - specifier: file_specifier, - maybe_headers: None, - source: file_source.into_bytes().into(), - }) - }) - .collect(); - - Ok(files) -} - -fn extract_files_from_source_comments( - specifier: &ModuleSpecifier, - source: Arc, - media_type: MediaType, -) -> Result, AnyError> { - let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.clone(), - text: source, - media_type, - capture_tokens: false, - maybe_syntax: None, - scope_analysis: false, - })?; - let comments = parsed_source.comments().get_vec(); - let blocks_regex = lazy_regex::regex!(r"```([^\r\n]*)\r?\n([\S\s]*?)```"); - let lines_regex = lazy_regex::regex!(r"(?:\* ?)(?:\# ?)?(.*)"); - - let files = comments - .iter() - .filter(|comment| { - if comment.kind != CommentKind::Block || !comment.text.starts_with('*') { - return false; - } - - true - }) - .flat_map(|comment| { - extract_files_from_regex_blocks( - specifier, - &comment.text, - media_type, - parsed_source.text_info_lazy().line_index(comment.start()), - blocks_regex, - lines_regex, - ) - }) - .flatten() - .collect(); - - Ok(files) -} - -fn extract_files_from_fenced_blocks( - specifier: &ModuleSpecifier, - source: &str, - media_type: MediaType, -) -> Result, AnyError> { - // The pattern matches code blocks as well as anything in HTML comment syntax, - // but it stores the latter without any capturing groups. This way, a simple - // check can be done to see if a block is inside a comment (and skip typechecking) - // or not by checking for the presence of capturing groups in the matches. - let blocks_regex = - lazy_regex::regex!(r"(?s)|```([^\r\n]*)\r?\n([\S\s]*?)```"); - let lines_regex = lazy_regex::regex!(r"(?:\# ?)?(.*)"); - - extract_files_from_regex_blocks( - specifier, - source, - media_type, - /* file line index */ 0, - blocks_regex, - lines_regex, - ) -} - -async fn fetch_inline_files( - file_fetcher: &FileFetcher, - specifiers: Vec, -) -> Result, AnyError> { - let mut files = Vec::new(); - for specifier in specifiers { - let fetch_permissions = PermissionsContainer::allow_all(); - let file = file_fetcher - .fetch(&specifier, &fetch_permissions) - .await? - .into_text_decoded()?; - - let inline_files = if file.media_type == MediaType::Unknown { - extract_files_from_fenced_blocks( - &file.specifier, - &file.source, - file.media_type, - ) - } else { - extract_files_from_source_comments( - &file.specifier, - file.source, - file.media_type, - ) - }; - - files.extend(inline_files?); - } - - Ok(files) -} - -/// Type check a collection of module and document specifiers. -pub async fn check_specifiers( - file_fetcher: &FileFetcher, - main_graph_container: &Arc, - specifiers: Vec<(ModuleSpecifier, TestMode)>, -) -> Result<(), AnyError> { - let inline_files = fetch_inline_files( - file_fetcher, - specifiers - .iter() - .filter_map(|(specifier, mode)| { - if *mode != TestMode::Executable { - Some(specifier.clone()) - } else { - None - } - }) - .collect(), - ) - .await?; - - let mut module_specifiers = specifiers - .into_iter() - .filter_map(|(specifier, mode)| { - if mode != TestMode::Documentation { - Some(specifier) - } else { - None - } - }) - .collect::>(); - - if !inline_files.is_empty() { - module_specifiers - .extend(inline_files.iter().map(|file| file.specifier.clone())); - - for file in inline_files { - file_fetcher.insert_memory_files(file); - } - } - - main_graph_container - .check_specifiers(&module_specifiers) - .await?; - - Ok(()) -} - static HAS_TEST_RUN_SIGINT_HANDLER: AtomicBool = AtomicBool::new(false); /// Test a collection of specifiers with test modes concurrently. @@ -1782,14 +1564,40 @@ pub async fn run_tests( return Err(generic_error("No test modules found")); } + let specifiers_for_typecheck_and_test = { + // Specifiers to extract doc tests from + let specifiers_needing_extraction = specifiers_with_mode + .iter() + .filter_map(|(s, mode)| mode.needs_test_extraction().then(|| s.clone())) + .collect::>(); + + let mut final_specifiers = specifiers_with_mode + .into_iter() + .filter_map(|(s, mode)| mode.needs_test_run().then(|| s)) + .collect::>(); + + // Extract doc tests, add them to the file fetcher as in-memory files, + // and add the specifiers to the final list of specifiers to run tests on. + for s in &specifiers_needing_extraction { + let file = file_fetcher + .fetch(s, &PermissionsContainer::allow_all()) + .await?; + let doc_tests = extract::extract_doc_tests(file)?; + for doc_test in doc_tests { + final_specifiers.push(doc_test.specifier.clone()); + file_fetcher.insert_memory_files(doc_test); + } + } + + final_specifiers + }; + let main_graph_container = factory.main_module_graph_container().await?; - check_specifiers( - file_fetcher, - main_graph_container, - specifiers_with_mode.clone(), - ) - .await?; + // Typecheck + main_graph_container + .check_specifiers(&specifiers_for_typecheck_and_test) + .await?; if workspace_test_options.no_run { return Ok(()); @@ -1798,16 +1606,11 @@ pub async fn run_tests( let worker_factory = Arc::new(factory.create_cli_main_worker_factory().await?); + // Run tests test_specifiers( worker_factory, &permissions, - specifiers_with_mode - .into_iter() - .filter_map(|(s, m)| match m { - TestMode::Documentation => None, - _ => Some(s), - }) - .collect(), + specifiers_for_typecheck_and_test, TestSpecifiersOptions { cwd: Url::from_directory_path(cli_options.initial_cwd()).map_err( |_| { @@ -1939,8 +1742,6 @@ pub async fn run_tests_with_watch( test_modules.clone() }; - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); let specifiers_with_mode = fetch_specifiers_with_test_mode( &cli_options, file_fetcher, @@ -1952,29 +1753,56 @@ pub async fn run_tests_with_watch( .filter(|(specifier, _)| test_modules_to_reload.contains(specifier)) .collect::>(); + let specifiers_for_typecheck_and_test = { + // Specifiers to extract doc tests from + let specifiers_needing_extraction = specifiers_with_mode + .iter() + .filter_map(|(s, mode)| { + mode.needs_test_extraction().then(|| s.clone()) + }) + .collect::>(); + + let mut final_specifiers = specifiers_with_mode + .into_iter() + .filter_map(|(s, mode)| mode.needs_test_run().then(|| s)) + .collect::>(); + + // Extract doc tests, add them to the file fetcher as in-memory files, + // and add the specifiers to the final list of specifiers to run tests + // on. + for s in &specifiers_needing_extraction { + let file = file_fetcher + .fetch(s, &PermissionsContainer::allow_all()) + .await?; + let doc_tests = extract::extract_doc_tests(file)?; + for doc_test in doc_tests { + final_specifiers.push(doc_test.specifier.clone()); + file_fetcher.insert_memory_files(doc_test); + } + } + + final_specifiers + }; + let main_graph_container = factory.main_module_graph_container().await?; - check_specifiers( - file_fetcher, - main_graph_container, - specifiers_with_mode.clone(), - ) - .await?; + + // Typecheck + main_graph_container + .check_specifiers(&specifiers_for_typecheck_and_test) + .await?; if workspace_test_options.no_run { return Ok(()); } + let worker_factory = + Arc::new(factory.create_cli_main_worker_factory().await?); + test_specifiers( worker_factory, &permissions, - specifiers_with_mode - .into_iter() - .filter_map(|(s, m)| match m { - TestMode::Documentation => None, - _ => Some(s), - }) - .collect(), + specifiers_for_typecheck_and_test, TestSpecifiersOptions { cwd: Url::from_directory_path(cli_options.initial_cwd()).map_err( |_| { From fb2b27e6669651324bb85545d97fe973fc455065 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Mon, 26 Aug 2024 20:19:26 +0900 Subject: [PATCH 02/31] fix integration tests --- tests/integration/test_tests.rs | 12 +++- tests/testdata/test/doc.out | 9 --- tests/testdata/test/doc.ts | 38 ------------ tests/testdata/test/doc_failure.out | 60 +++++++++++++++++++ tests/testdata/test/doc_failure.ts | 32 ++++++++++ tests/testdata/test/doc_success.out | 19 ++++++ tests/testdata/test/doc_success.ts | 50 ++++++++++++++++ tests/testdata/test/markdown.out | 6 +- .../test/markdown_full_block_names.out | 6 +- tests/testdata/test/markdown_windows.out | 6 +- tests/testdata/test/markdown_with_comment.out | 6 +- 11 files changed, 182 insertions(+), 62 deletions(-) delete mode 100644 tests/testdata/test/doc.out delete mode 100644 tests/testdata/test/doc.ts create mode 100644 tests/testdata/test/doc_failure.out create mode 100644 tests/testdata/test/doc_failure.ts create mode 100644 tests/testdata/test/doc_success.out create mode 100644 tests/testdata/test/doc_success.ts diff --git a/tests/integration/test_tests.rs b/tests/integration/test_tests.rs index 175753f4ad6607..47fb2817ee1271 100644 --- a/tests/integration/test_tests.rs +++ b/tests/integration/test_tests.rs @@ -178,10 +178,16 @@ itest!(interval { output: "test/interval.out", }); -itest!(doc { - args: "test --doc --allow-all test/doc.ts", +itest!(doc_success { + args: "test --doc --config ../config/deno.json test/doc_success.ts", + exit_code: 0, + output: "test/doc_success.out", +}); + +itest!(doc_failure { + args: "test --doc --config ../config/deno.json test/doc_failure.ts", exit_code: 1, - output: "test/doc.out", + output: "test/doc_failure.out", }); itest!(doc_only { diff --git a/tests/testdata/test/doc.out b/tests/testdata/test/doc.out deleted file mode 100644 index cd8bbb62039e96..00000000000000 --- a/tests/testdata/test/doc.out +++ /dev/null @@ -1,9 +0,0 @@ -Check [WILDCARD]/doc.ts$6-9.js -Check [WILDCARD]/doc.ts$10-13.jsx -Check [WILDCARD]/doc.ts$14-17.ts -Check [WILDCARD]/doc.ts$18-21.tsx -Check [WILDCARD]/doc.ts$30-35.ts -error: TS2367 [ERROR]: This comparison appears to be unintentional because the types 'string' and 'number' have no overlap. -console.assert(check() == 42); - ~~~~~~~~~~~~~ - at [WILDCARD]/doc.ts$30-35.ts:3:16 diff --git a/tests/testdata/test/doc.ts b/tests/testdata/test/doc.ts deleted file mode 100644 index 519479fc524f4c..00000000000000 --- a/tests/testdata/test/doc.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * ``` - * import * as doc from "./doc.ts"; - * ``` - * - * ```js - * import * as doc from "./doc.ts"; - * ``` - * - * ```jsx - * import * as doc from "./doc.ts"; - * ``` - * - * ```ts - * import * as doc from "./doc.ts"; - * ``` - * - * ```tsx - * import * as doc from "./doc.ts"; - * ``` - * - * ```text - * import * as doc from "./doc.ts"; - * ``` - * - * @module doc - */ - -/** - * ```ts - * import { check } from "./doc.ts"; - * - * console.assert(check() == 42); - * ``` - */ -export function check(): string { - return "check"; -} diff --git a/tests/testdata/test/doc_failure.out b/tests/testdata/test/doc_failure.out new file mode 100644 index 00000000000000..169ed661dc756b --- /dev/null +++ b/tests/testdata/test/doc_failure.out @@ -0,0 +1,60 @@ +Check [WILDCARD]/tests/testdata/test/doc_failure.ts +Check [WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts +Check [WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts +Check [WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts +running 0 tests from ./test/doc_failure.ts +running 1 test from ./test/doc_failure.ts$2-9.ts +[WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts ... FAILED ([WILDCARD]ms) +running 1 test from ./test/doc_failure.ts$13-18.ts +[WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts ... FAILED ([WILDCARD]ms) +running 1 test from ./test/doc_failure.ts$24-29.ts +[WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts ... FAILED ([WILDCARD]ms) + + ERRORS + +[WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts => ./test/doc_failure.ts$13-18.ts:3:6 +error: AssertionError: Values are not equal. + + + [Diff] Actual / Expected + + +- 3 ++ 4 + + throw new AssertionError(message); + ^ + at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) + at [WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts:4:5 + +[WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts => ./test/doc_failure.ts$2-9.ts:3:6 +error: AssertionError: Expected actual: "2.5e+0" to be close to "2": delta "5e-1" is greater than "2e-7". + throw new AssertionError( + ^ + at assertAlmostEquals ([WILDCARD]/tests/util/std/assert/almost_equals.ts:51:9) + at [WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts:6:5 + +[WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts => ./test/doc_failure.ts$24-29.ts:3:6 +error: AssertionError: Values are not equal. + + + [Diff] Actual / Expected + + +- 4 ++ 3 + + throw new AssertionError(message); + ^ + at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) + at [WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts:4:5 + + FAILURES + +[WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts => ./test/doc_failure.ts$13-18.ts:3:6 +[WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts => ./test/doc_failure.ts$2-9.ts:3:6 +[WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts => ./test/doc_failure.ts$24-29.ts:3:6 + +FAILED | 0 passed | 3 failed ([WILDCARD]ms) + +error: Test failed diff --git a/tests/testdata/test/doc_failure.ts b/tests/testdata/test/doc_failure.ts new file mode 100644 index 00000000000000..2acdf67d465f54 --- /dev/null +++ b/tests/testdata/test/doc_failure.ts @@ -0,0 +1,32 @@ +/** + * ```ts + * import { assertAlmostEquals } from "@std/assert/almost-equals"; + * + * const x = sub(3, 1); + * const y = div(5, x); + * assertAlmostEquals(y, 2.0); // throws + * ``` + * @module doc + */ + +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(div(6, 2), 4); // throws + * ``` + */ +export function div(a: number, b: number): number { + return a / b; +} + +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(sub(6, 2), 3); // throws + * ``` + */ +const sub = (a: number, b: number): number => a - b; + +export { sub }; diff --git a/tests/testdata/test/doc_success.out b/tests/testdata/test/doc_success.out new file mode 100644 index 00000000000000..18aaf70261db0f --- /dev/null +++ b/tests/testdata/test/doc_success.out @@ -0,0 +1,19 @@ +Check [WILDCARD]/doc_success.ts$8-13.js +Check [WILDCARD]/doc_success.ts$14-19.jsx +Check [WILDCARD]/doc_success.ts$20-25.ts +Check [WILDCARD]/doc_success.ts$26-31.tsx +Check [WILDCARD]/doc_success.ts$42-47.ts +running 0 tests from ./test/doc_success.ts +running 1 test from ./test/doc_success.ts$8-13.js +[WILDCARD]/tests/testdata/test/doc_success.ts$8-13.js ... ok ([WILDCARD]ms) +running 1 test from ./test/doc_success.ts$14-19.jsx +[WILDCARD]/tests/testdata/test/doc_success.ts$14-19.jsx ... ok ([WILDCARD]ms) +running 1 test from ./test/doc_success.ts$20-25.ts +[WILDCARD]/tests/testdata/test/doc_success.ts$20-25.ts ... ok ([WILDCARD]ms) +running 1 test from ./test/doc_success.ts$26-31.tsx +[WILDCARD]/tests/testdata/test/doc_success.ts$26-31.tsx ... ok ([WILDCARD]ms) +running 1 test from ./test/doc_success.ts$42-47.ts +[WILDCARD]/tests/testdata/test/doc_success.ts$42-47.ts ... ok ([WILDCARD]ms) + +ok | 5 passed | 0 failed ([WILDCARD]ms) + diff --git a/tests/testdata/test/doc_success.ts b/tests/testdata/test/doc_success.ts new file mode 100644 index 00000000000000..6ab339ca4f3d01 --- /dev/null +++ b/tests/testdata/test/doc_success.ts @@ -0,0 +1,50 @@ +/** + * ``` + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + * + * ```js + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + * + * ```jsx + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + * + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + * + * ```tsx + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + * + * ```text + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + * + * @module doc + */ + +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} diff --git a/tests/testdata/test/markdown.out b/tests/testdata/test/markdown.out index 38c9f0349f5bdc..2a65bc20f60468 100644 --- a/tests/testdata/test/markdown.out +++ b/tests/testdata/test/markdown.out @@ -2,6 +2,6 @@ Check [WILDCARD]/test/markdown.md$11-14.js Check [WILDCARD]/test/markdown.md$17-20.ts Check [WILDCARD]/test/markdown.md$29-32.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. -const a: string = 42; - ^ - at [WILDCARD]/test/markdown.md$29-32.ts:1:7 + const a: string = 42; + ^ + at [WILDCARD]/test/markdown.md$29-32.ts:2:11 diff --git a/tests/testdata/test/markdown_full_block_names.out b/tests/testdata/test/markdown_full_block_names.out index 13051112ea485e..b332b3096f6a23 100644 --- a/tests/testdata/test/markdown_full_block_names.out +++ b/tests/testdata/test/markdown_full_block_names.out @@ -1,6 +1,6 @@ Check [WILDCARD]/test/markdown_full_block_names.md$5-8.js Check [WILDCARD]/test/markdown_full_block_names.md$17-20.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. -const a: string = 42; - ^ - at [WILDCARD]/test/markdown_full_block_names.md$17-20.ts:1:7 + const a: string = 42; + ^ + at [WILDCARD]/test/markdown_full_block_names.md$17-20.ts:2:11 diff --git a/tests/testdata/test/markdown_windows.out b/tests/testdata/test/markdown_windows.out index 4810e50cd39eee..8baaf664bbae9e 100644 --- a/tests/testdata/test/markdown_windows.out +++ b/tests/testdata/test/markdown_windows.out @@ -2,6 +2,6 @@ Check [WILDCARD]/test/markdown_windows.md$11-14.js Check [WILDCARD]/test/markdown_windows.md$17-20.ts Check [WILDCARD]/test/markdown_windows.md$29-32.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. -const a: string = 42; - ^ - at [WILDCARD]/test/markdown_windows.md$29-32.ts:1:7 + const a: string = 42; + ^ + at [WILDCARD]/test/markdown_windows.md$29-32.ts:2:11 diff --git a/tests/testdata/test/markdown_with_comment.out b/tests/testdata/test/markdown_with_comment.out index b202919d8fd216..a7a7efa11ec0e1 100644 --- a/tests/testdata/test/markdown_with_comment.out +++ b/tests/testdata/test/markdown_with_comment.out @@ -1,5 +1,5 @@ Check [WILDCARD]/test/markdown_with_comment.md$34-37.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. -const a: string = 42; - ^ - at [WILDCARD]/test/markdown_with_comment.md$34-37.ts:1:7 + const a: string = 42; + ^ + at [WILDCARD]/test/markdown_with_comment.md$34-37.ts:2:11 From b95d187b7802d8a76c4c8e5012101bed844578b2 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 27 Aug 2024 00:35:01 +0900 Subject: [PATCH 03/31] enrich test_watch_doc --- tests/integration/watcher_tests.rs | 99 +++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/tests/integration/watcher_tests.rs b/tests/integration/watcher_tests.rs index 9685d7ae8b84ef..a78345c0364d89 100644 --- a/tests/integration/watcher_tests.rs +++ b/tests/integration/watcher_tests.rs @@ -1012,7 +1012,6 @@ async fn test_watch_basic() { .current_dir(t.path()) .arg("test") .arg("--watch") - .arg("--unstable") .arg("--no-check") .arg(t.path()) .env("NO_COLOR", "1") @@ -1166,9 +1165,10 @@ async fn test_watch_doc() { let mut child = util::deno_cmd() .current_dir(t.path()) .arg("test") + .arg("--config") + .arg(util::deno_config_path()) .arg("--watch") .arg("--doc") - .arg("--unstable") .arg(t.path()) .env("NO_COLOR", "1") .piped_output() @@ -1186,24 +1186,107 @@ async fn test_watch_doc() { let foo_file = t.path().join("foo.ts"); foo_file.write( r#" - export default function foo() {} + export function add(a: number, b: number) { + return a + b; + } "#, ); + wait_contains("ok | 0 passed | 0 failed", &mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; + + // Trigger a type error foo_file.write( r#" /** * ```ts - * import foo from "./foo.ts"; + * const sum: string = add(1, 2); * ``` */ - export default function foo() {} + export function add(a: number, b: number) { + return a + b; + } "#, ); - // We only need to scan for a Check file://.../foo.ts$3-6 line that - // corresponds to the documentation block being type-checked. - assert_contains!(skip_restarting_line(&mut stderr_lines).await, "foo.ts$3-6"); + assert_eq!( + skip_restarting_line(&mut stderr_lines).await, + format!("Check file://{foo_file}$3-6.ts") + ); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + "error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'." + ); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + " const sum: string = add(1, 2);" + ); + assert_eq!(next_line(&mut stderr_lines).await.unwrap(), " ~~~"); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + format!(" at file://{foo_file}$3-6.ts:3:11") + ); + wait_contains("Test failed", &mut stderr_lines).await; + + // Trigger a runtime error + foo_file.write( + r#" + /** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 4); + * ``` + */ + export function add(a: number, b: number) { + return a + b; + } + "#, + ); + + wait_contains("running 1 test from", &mut stdout_lines).await; + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + &format!("{foo_file}$3-8.ts ... FAILED") + ); + wait_contains("ERRORS", &mut stdout_lines).await; + wait_contains( + "error: AssertionError: Values are not equal.", + &mut stdout_lines, + ) + .await; + wait_contains("- 3", &mut stdout_lines).await; + wait_contains("+ 4", &mut stdout_lines).await; + wait_contains("FAILURES", &mut stdout_lines).await; + wait_contains("FAILED | 0 passed | 1 failed", &mut stdout_lines).await; + + wait_contains("Test failed", &mut stderr_lines).await; + + // Fix the runtime error + foo_file.write( + r#" + /** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + */ + export function add(a: number, b: number) { + return a + b; + } + "#, + ); + + wait_contains(&format!("running 1 test from"), &mut stdout_lines).await; + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + &format!("file://{foo_file}$3-8.ts ... ok") + ); + wait_contains("ok | 1 passed | 0 failed", &mut stdout_lines).await; + + wait_contains("Test finished", &mut stderr_lines).await; + check_alive_then_kill(child); } From 26497e268c502c1abeeed0f886919ad5c8ef6ba6 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 27 Aug 2024 00:45:20 +0900 Subject: [PATCH 04/31] fmt and clippy --- cli/tools/test/extract.rs | 24 +++++++++++------------- cli/tools/test/mod.rs | 12 ++++++------ tests/integration/watcher_tests.rs | 2 +- tests/testdata/test/doc_failure.ts | 2 +- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/cli/tools/test/extract.rs b/cli/tools/test/extract.rs index 913a3c380fd323..deecaf3a932e46 100644 --- a/cli/tools/test/extract.rs +++ b/cli/tools/test/extract.rs @@ -56,14 +56,12 @@ pub(super) fn extract_doc_tests(file: File) -> Result, AnyError> { )? }; - Ok( - extracted_files - .into_iter() - .map(|extracted_file| { - generate_pseudo_test_file(extracted_file, &file.specifier, &exports) - }) - .collect::>()?, - ) + extracted_files + .into_iter() + .map(|extracted_file| { + generate_pseudo_test_file(extracted_file, &file.specifier, &exports) + }) + .collect::>() } fn extract_files_from_fenced_blocks( @@ -350,12 +348,12 @@ fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { atoms.push(binding_ident.sym.clone()); } ast::Pat::Array(array_pat) => { - for elem in array_pat.elems.iter().cloned().filter_map(|e| e) { - rec(&elem, atoms); + for elem in array_pat.elems.iter().flatten() { + rec(elem, atoms); } } ast::Pat::Rest(rest_pat) => { - rec(&*rest_pat.arg, atoms); + rec(&rest_pat.arg, atoms); } ast::Pat::Object(object_pat) => { for prop in &object_pat.props { @@ -367,13 +365,13 @@ fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { rec(&key_value_pat_prop.value, atoms); } ast::ObjectPatProp::Rest(rest_pat) => { - rec(&*rest_pat.arg, atoms); + rec(&rest_pat.arg, atoms); } } } } ast::Pat::Assign(assign_pat) => { - rec(&*assign_pat.left, atoms); + rec(&assign_pat.left, atoms); } ast::Pat::Invalid(_) | ast::Pat::Expr(_) => {} } diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 05184bf770733a..3a7540c65dbdaa 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1568,12 +1568,13 @@ pub async fn run_tests( // Specifiers to extract doc tests from let specifiers_needing_extraction = specifiers_with_mode .iter() - .filter_map(|(s, mode)| mode.needs_test_extraction().then(|| s.clone())) + .filter(|(_, mode)| mode.needs_test_extraction()) + .map(|(s, _)| s.clone()) .collect::>(); let mut final_specifiers = specifiers_with_mode .into_iter() - .filter_map(|(s, mode)| mode.needs_test_run().then(|| s)) + .filter_map(|(s, mode)| mode.needs_test_run().then_some(s)) .collect::>(); // Extract doc tests, add them to the file fetcher as in-memory files, @@ -1757,14 +1758,13 @@ pub async fn run_tests_with_watch( // Specifiers to extract doc tests from let specifiers_needing_extraction = specifiers_with_mode .iter() - .filter_map(|(s, mode)| { - mode.needs_test_extraction().then(|| s.clone()) - }) + .filter(|(_, mode)| mode.needs_test_extraction()) + .map(|(s, _)| s.clone()) .collect::>(); let mut final_specifiers = specifiers_with_mode .into_iter() - .filter_map(|(s, mode)| mode.needs_test_run().then(|| s)) + .filter_map(|(s, mode)| mode.needs_test_run().then_some(s)) .collect::>(); // Extract doc tests, add them to the file fetcher as in-memory files, diff --git a/tests/integration/watcher_tests.rs b/tests/integration/watcher_tests.rs index a78345c0364d89..0fd62591e89dae 100644 --- a/tests/integration/watcher_tests.rs +++ b/tests/integration/watcher_tests.rs @@ -1278,7 +1278,7 @@ async fn test_watch_doc() { "#, ); - wait_contains(&format!("running 1 test from"), &mut stdout_lines).await; + wait_contains("running 1 test from", &mut stdout_lines).await; assert_contains!( next_line(&mut stdout_lines).await.unwrap(), &format!("file://{foo_file}$3-8.ts ... ok") diff --git a/tests/testdata/test/doc_failure.ts b/tests/testdata/test/doc_failure.ts index 2acdf67d465f54..7ec678c034585a 100644 --- a/tests/testdata/test/doc_failure.ts +++ b/tests/testdata/test/doc_failure.ts @@ -17,7 +17,7 @@ * ``` */ export function div(a: number, b: number): number { - return a / b; + return a / b; } /** From 20b2d0650ad77f9dda73d6c78011f7b40051fbd2 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 27 Aug 2024 01:36:50 +0900 Subject: [PATCH 05/31] migrate itest! to specs --- tests/integration/test_tests.rs | 42 ------------- tests/specs/test/doc_failure/__test__.jsonc | 5 ++ tests/specs/test/doc_failure/main.out | 60 +++++++++++++++++++ .../test/doc_failure/main.ts} | 0 tests/specs/test/doc_only/__test__.jsonc | 5 ++ .../test/doc_only/dir}/mod.ts | 4 +- tests/specs/test/doc_only/doc_only.out | 6 ++ tests/specs/test/doc_success/__test__.jsonc | 5 ++ tests/specs/test/doc_success/main.out | 19 ++++++ .../test/doc_success/main.ts} | 0 tests/specs/test/markdown/__test__.jsonc | 5 ++ .../test => specs/test/markdown}/markdown.md | 0 tests/specs/test/markdown/markdown.out | 7 +++ .../markdown_full_block_names/__test__.jsonc | 5 ++ .../markdown_full_block_names.md | 0 .../markdown_full_block_names.out | 6 ++ .../__test__.jsonc | 5 ++ .../markdown_with_comment.md | 0 .../markdown_with_comment.out | 5 ++ .../test/markdown_windows/__test__.jsonc | 5 ++ .../markdown_windows}/markdown_windows.md | 0 .../markdown_windows/markdown_windows.out | 7 +++ tests/testdata/test/doc_failure.out | 60 ------------------- tests/testdata/test/doc_only.out | 4 -- tests/testdata/test/doc_success.out | 19 ------ tests/testdata/test/markdown.out | 7 --- .../test/markdown_full_block_names.out | 6 -- tests/testdata/test/markdown_windows.out | 7 --- tests/testdata/test/markdown_with_comment.out | 5 -- 29 files changed, 148 insertions(+), 151 deletions(-) create mode 100644 tests/specs/test/doc_failure/__test__.jsonc create mode 100644 tests/specs/test/doc_failure/main.out rename tests/{testdata/test/doc_failure.ts => specs/test/doc_failure/main.ts} (100%) create mode 100644 tests/specs/test/doc_only/__test__.jsonc rename tests/{testdata/test/doc_only => specs/test/doc_only/dir}/mod.ts (69%) create mode 100644 tests/specs/test/doc_only/doc_only.out create mode 100644 tests/specs/test/doc_success/__test__.jsonc create mode 100644 tests/specs/test/doc_success/main.out rename tests/{testdata/test/doc_success.ts => specs/test/doc_success/main.ts} (100%) create mode 100644 tests/specs/test/markdown/__test__.jsonc rename tests/{testdata/test => specs/test/markdown}/markdown.md (100%) create mode 100644 tests/specs/test/markdown/markdown.out create mode 100644 tests/specs/test/markdown_full_block_names/__test__.jsonc rename tests/{testdata/test => specs/test/markdown_full_block_names}/markdown_full_block_names.md (100%) create mode 100644 tests/specs/test/markdown_full_block_names/markdown_full_block_names.out create mode 100644 tests/specs/test/markdown_ignore_html_comment/__test__.jsonc rename tests/{testdata/test => specs/test/markdown_ignore_html_comment}/markdown_with_comment.md (100%) create mode 100644 tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out create mode 100644 tests/specs/test/markdown_windows/__test__.jsonc rename tests/{testdata/test => specs/test/markdown_windows}/markdown_windows.md (100%) create mode 100644 tests/specs/test/markdown_windows/markdown_windows.out delete mode 100644 tests/testdata/test/doc_failure.out delete mode 100644 tests/testdata/test/doc_only.out delete mode 100644 tests/testdata/test/doc_success.out delete mode 100644 tests/testdata/test/markdown.out delete mode 100644 tests/testdata/test/markdown_full_block_names.out delete mode 100644 tests/testdata/test/markdown_windows.out delete mode 100644 tests/testdata/test/markdown_with_comment.out diff --git a/tests/integration/test_tests.rs b/tests/integration/test_tests.rs index 47fb2817ee1271..fa42866f48f9b6 100644 --- a/tests/integration/test_tests.rs +++ b/tests/integration/test_tests.rs @@ -178,48 +178,6 @@ itest!(interval { output: "test/interval.out", }); -itest!(doc_success { - args: "test --doc --config ../config/deno.json test/doc_success.ts", - exit_code: 0, - output: "test/doc_success.out", -}); - -itest!(doc_failure { - args: "test --doc --config ../config/deno.json test/doc_failure.ts", - exit_code: 1, - output: "test/doc_failure.out", -}); - -itest!(doc_only { - args: "test --doc --allow-all test/doc_only", - exit_code: 0, - output: "test/doc_only.out", -}); - -itest!(markdown { - args: "test --doc --allow-all test/markdown.md", - exit_code: 1, - output: "test/markdown.out", -}); - -itest!(markdown_windows { - args: "test --doc --allow-all test/markdown_windows.md", - exit_code: 1, - output: "test/markdown_windows.out", -}); - -itest!(markdown_full_block_names { - args: "test --doc --allow-all test/markdown_full_block_names.md", - exit_code: 1, - output: "test/markdown_full_block_names.out", -}); - -itest!(markdown_ignore_html_comment { - args: "test --doc --allow-all test/markdown_with_comment.md", - exit_code: 1, - output: "test/markdown_with_comment.out", -}); - itest!(text { args: "test --doc --allow-all test/text.md", exit_code: 0, diff --git a/tests/specs/test/doc_failure/__test__.jsonc b/tests/specs/test/doc_failure/__test__.jsonc new file mode 100644 index 00000000000000..79f16ad8d8d72a --- /dev/null +++ b/tests/specs/test/doc_failure/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --config ../../../config/deno.json main.ts", + "exitCode": 1, + "output": "main.out" +} diff --git a/tests/specs/test/doc_failure/main.out b/tests/specs/test/doc_failure/main.out new file mode 100644 index 00000000000000..b7aa0d0f203772 --- /dev/null +++ b/tests/specs/test/doc_failure/main.out @@ -0,0 +1,60 @@ +Check [WILDCARD]/tests/specs/test/doc_failure/main.ts +Check [WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts +Check [WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts +Check [WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts +running 0 tests from ./main.ts +running 1 test from ./main.ts$2-9.ts +[WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts ... FAILED ([WILDCARD]ms) +running 1 test from ./main.ts$13-18.ts +[WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts ... FAILED ([WILDCARD]ms) +running 1 test from ./main.ts$24-29.ts +[WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts ... FAILED ([WILDCARD]ms) + + ERRORS + +[WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts => ./main.ts$13-18.ts:3:6 +error: AssertionError: Values are not equal. + + + [Diff] Actual / Expected + + +- 3 ++ 4 + + throw new AssertionError(message); + ^ + at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) + at [WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts:4:5 + +[WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 +error: AssertionError: Expected actual: "2.5e+0" to be close to "2": delta "5e-1" is greater than "2e-7". + throw new AssertionError( + ^ + at assertAlmostEquals ([WILDCARD]/tests/util/std/assert/almost_equals.ts:51:9) + at [WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts:6:5 + +[WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 +error: AssertionError: Values are not equal. + + + [Diff] Actual / Expected + + +- 4 ++ 3 + + throw new AssertionError(message); + ^ + at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) + at [WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts:4:5 + + FAILURES + +[WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts => ./main.ts$13-18.ts:3:6 +[WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 +[WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 + +FAILED | 0 passed | 3 failed ([WILDCARD]ms) + +error: Test failed diff --git a/tests/testdata/test/doc_failure.ts b/tests/specs/test/doc_failure/main.ts similarity index 100% rename from tests/testdata/test/doc_failure.ts rename to tests/specs/test/doc_failure/main.ts diff --git a/tests/specs/test/doc_only/__test__.jsonc b/tests/specs/test/doc_only/__test__.jsonc new file mode 100644 index 00000000000000..b826770b6d75f8 --- /dev/null +++ b/tests/specs/test/doc_only/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --config ../../../config/deno.json dir", + "exitCode": 0, + "output": "doc_only.out" +} diff --git a/tests/testdata/test/doc_only/mod.ts b/tests/specs/test/doc_only/dir/mod.ts similarity index 69% rename from tests/testdata/test/doc_only/mod.ts rename to tests/specs/test/doc_only/dir/mod.ts index 467d850a27fb1d..a389302ce8b092 100644 --- a/tests/testdata/test/doc_only/mod.ts +++ b/tests/specs/test/doc_only/dir/mod.ts @@ -1,6 +1,8 @@ /** * ```ts - * import "./mod.ts"; + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(42, 40 + 2); * ``` */ Deno.test("unreachable", function () { diff --git a/tests/specs/test/doc_only/doc_only.out b/tests/specs/test/doc_only/doc_only.out new file mode 100644 index 00000000000000..b73301e200da3e --- /dev/null +++ b/tests/specs/test/doc_only/doc_only.out @@ -0,0 +1,6 @@ +Check [WILDCARD]/specs/test/doc_only/dir/mod.ts$2-7.ts +running 1 test from ./dir/mod.ts$2-7.ts +[WILDCARD]/specs/test/doc_only/dir/mod.ts$2-7.ts ... ok ([WILDCARD]ms) + +ok | 1 passed | 0 failed ([WILDCARD]ms) + diff --git a/tests/specs/test/doc_success/__test__.jsonc b/tests/specs/test/doc_success/__test__.jsonc new file mode 100644 index 00000000000000..2a8e6aafca60b5 --- /dev/null +++ b/tests/specs/test/doc_success/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --config ../../../config/deno.json main.ts", + "exitCode": 0, + "output": "main.out" +} diff --git a/tests/specs/test/doc_success/main.out b/tests/specs/test/doc_success/main.out new file mode 100644 index 00000000000000..77aa702ddfd0fa --- /dev/null +++ b/tests/specs/test/doc_success/main.out @@ -0,0 +1,19 @@ +Check [WILDCARD]/specs/test/doc_success/main.ts$8-13.js +Check [WILDCARD]/specs/test/doc_success/main.ts$14-19.jsx +Check [WILDCARD]/specs/test/doc_success/main.ts$20-25.ts +Check [WILDCARD]/specs/test/doc_success/main.ts$26-31.tsx +Check [WILDCARD]/specs/test/doc_success/main.ts$42-47.ts +running 0 tests from ./main.ts +running 1 test from ./main.ts$8-13.js +[WILDCARD]/specs/test/doc_success/main.ts$8-13.js ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$14-19.jsx +[WILDCARD]/specs/test/doc_success/main.ts$14-19.jsx ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$20-25.ts +[WILDCARD]/specs/test/doc_success/main.ts$20-25.ts ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$26-31.tsx +[WILDCARD]/specs/test/doc_success/main.ts$26-31.tsx ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$42-47.ts +[WILDCARD]/specs/test/doc_success/main.ts$42-47.ts ... ok ([WILDCARD]ms) + +ok | 5 passed | 0 failed ([WILDCARD]ms) + diff --git a/tests/testdata/test/doc_success.ts b/tests/specs/test/doc_success/main.ts similarity index 100% rename from tests/testdata/test/doc_success.ts rename to tests/specs/test/doc_success/main.ts diff --git a/tests/specs/test/markdown/__test__.jsonc b/tests/specs/test/markdown/__test__.jsonc new file mode 100644 index 00000000000000..731da240757e23 --- /dev/null +++ b/tests/specs/test/markdown/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc markdown.md", + "exitCode": 1, + "output": "markdown.out" +} diff --git a/tests/testdata/test/markdown.md b/tests/specs/test/markdown/markdown.md similarity index 100% rename from tests/testdata/test/markdown.md rename to tests/specs/test/markdown/markdown.md diff --git a/tests/specs/test/markdown/markdown.out b/tests/specs/test/markdown/markdown.out new file mode 100644 index 00000000000000..e90f05be40723b --- /dev/null +++ b/tests/specs/test/markdown/markdown.out @@ -0,0 +1,7 @@ +Check [WILDCARD]/specs/test/markdown/markdown.md$11-14.js +Check [WILDCARD]/specs/test/markdown/markdown.md$17-20.ts +Check [WILDCARD]/specs/test/markdown/markdown.md$29-32.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. + const a: string = 42; + ^ + at [WILDCARD]/specs/test/markdown/markdown.md$29-32.ts:2:11 diff --git a/tests/specs/test/markdown_full_block_names/__test__.jsonc b/tests/specs/test/markdown_full_block_names/__test__.jsonc new file mode 100644 index 00000000000000..241dcd3af0c819 --- /dev/null +++ b/tests/specs/test/markdown_full_block_names/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc markdown_full_block_names.md", + "exitCode": 1, + "output": "markdown_full_block_names.out" +} diff --git a/tests/testdata/test/markdown_full_block_names.md b/tests/specs/test/markdown_full_block_names/markdown_full_block_names.md similarity index 100% rename from tests/testdata/test/markdown_full_block_names.md rename to tests/specs/test/markdown_full_block_names/markdown_full_block_names.md diff --git a/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out b/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out new file mode 100644 index 00000000000000..a4630a5696479f --- /dev/null +++ b/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out @@ -0,0 +1,6 @@ +Check [WILDCARD]/specs/test/markdown_full_block_names/markdown_full_block_names.md$5-8.js +Check [WILDCARD]/specs/test/markdown_full_block_names/markdown_full_block_names.md$17-20.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. + const a: string = 42; + ^ + at [WILDCARD]/specs/test/markdown_full_block_names/markdown_full_block_names.md$17-20.ts:2:11 diff --git a/tests/specs/test/markdown_ignore_html_comment/__test__.jsonc b/tests/specs/test/markdown_ignore_html_comment/__test__.jsonc new file mode 100644 index 00000000000000..2b6f1194b3f495 --- /dev/null +++ b/tests/specs/test/markdown_ignore_html_comment/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc markdown_with_comment.md", + "exitCode": 1, + "output": "markdown_with_comment.out" +} diff --git a/tests/testdata/test/markdown_with_comment.md b/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.md similarity index 100% rename from tests/testdata/test/markdown_with_comment.md rename to tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.md diff --git a/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out b/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out new file mode 100644 index 00000000000000..00511dcee37444 --- /dev/null +++ b/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out @@ -0,0 +1,5 @@ +Check [WILDCARD]/specs/test/markdown_ignore_html_comment/markdown_with_comment.md$34-37.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. + const a: string = 42; + ^ + at [WILDCARD]/test/markdown_ignore_html_comment/markdown_with_comment.md$34-37.ts:2:11 diff --git a/tests/specs/test/markdown_windows/__test__.jsonc b/tests/specs/test/markdown_windows/__test__.jsonc new file mode 100644 index 00000000000000..b8a524f5550ebb --- /dev/null +++ b/tests/specs/test/markdown_windows/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc markdown_windows.md", + "exitCode": 1, + "output": "markdown_windows.out" +} diff --git a/tests/testdata/test/markdown_windows.md b/tests/specs/test/markdown_windows/markdown_windows.md similarity index 100% rename from tests/testdata/test/markdown_windows.md rename to tests/specs/test/markdown_windows/markdown_windows.md diff --git a/tests/specs/test/markdown_windows/markdown_windows.out b/tests/specs/test/markdown_windows/markdown_windows.out new file mode 100644 index 00000000000000..0de7fce70c395e --- /dev/null +++ b/tests/specs/test/markdown_windows/markdown_windows.out @@ -0,0 +1,7 @@ +Check [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$11-14.js +Check [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$17-20.ts +Check [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$29-32.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. + const a: string = 42; + ^ + at [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$29-32.ts:2:11 diff --git a/tests/testdata/test/doc_failure.out b/tests/testdata/test/doc_failure.out deleted file mode 100644 index 169ed661dc756b..00000000000000 --- a/tests/testdata/test/doc_failure.out +++ /dev/null @@ -1,60 +0,0 @@ -Check [WILDCARD]/tests/testdata/test/doc_failure.ts -Check [WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts -Check [WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts -Check [WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts -running 0 tests from ./test/doc_failure.ts -running 1 test from ./test/doc_failure.ts$2-9.ts -[WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts ... FAILED ([WILDCARD]ms) -running 1 test from ./test/doc_failure.ts$13-18.ts -[WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts ... FAILED ([WILDCARD]ms) -running 1 test from ./test/doc_failure.ts$24-29.ts -[WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts ... FAILED ([WILDCARD]ms) - - ERRORS - -[WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts => ./test/doc_failure.ts$13-18.ts:3:6 -error: AssertionError: Values are not equal. - - - [Diff] Actual / Expected - - -- 3 -+ 4 - - throw new AssertionError(message); - ^ - at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) - at [WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts:4:5 - -[WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts => ./test/doc_failure.ts$2-9.ts:3:6 -error: AssertionError: Expected actual: "2.5e+0" to be close to "2": delta "5e-1" is greater than "2e-7". - throw new AssertionError( - ^ - at assertAlmostEquals ([WILDCARD]/tests/util/std/assert/almost_equals.ts:51:9) - at [WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts:6:5 - -[WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts => ./test/doc_failure.ts$24-29.ts:3:6 -error: AssertionError: Values are not equal. - - - [Diff] Actual / Expected - - -- 4 -+ 3 - - throw new AssertionError(message); - ^ - at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) - at [WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts:4:5 - - FAILURES - -[WILDCARD]/tests/testdata/test/doc_failure.ts$13-18.ts => ./test/doc_failure.ts$13-18.ts:3:6 -[WILDCARD]/tests/testdata/test/doc_failure.ts$2-9.ts => ./test/doc_failure.ts$2-9.ts:3:6 -[WILDCARD]/tests/testdata/test/doc_failure.ts$24-29.ts => ./test/doc_failure.ts$24-29.ts:3:6 - -FAILED | 0 passed | 3 failed ([WILDCARD]ms) - -error: Test failed diff --git a/tests/testdata/test/doc_only.out b/tests/testdata/test/doc_only.out deleted file mode 100644 index 2b8b6dc73592ac..00000000000000 --- a/tests/testdata/test/doc_only.out +++ /dev/null @@ -1,4 +0,0 @@ -Check [WILDCARD]/test/doc_only/mod.ts$2-5.ts - -ok | 0 passed | 0 failed ([WILDCARD]) - diff --git a/tests/testdata/test/doc_success.out b/tests/testdata/test/doc_success.out deleted file mode 100644 index 18aaf70261db0f..00000000000000 --- a/tests/testdata/test/doc_success.out +++ /dev/null @@ -1,19 +0,0 @@ -Check [WILDCARD]/doc_success.ts$8-13.js -Check [WILDCARD]/doc_success.ts$14-19.jsx -Check [WILDCARD]/doc_success.ts$20-25.ts -Check [WILDCARD]/doc_success.ts$26-31.tsx -Check [WILDCARD]/doc_success.ts$42-47.ts -running 0 tests from ./test/doc_success.ts -running 1 test from ./test/doc_success.ts$8-13.js -[WILDCARD]/tests/testdata/test/doc_success.ts$8-13.js ... ok ([WILDCARD]ms) -running 1 test from ./test/doc_success.ts$14-19.jsx -[WILDCARD]/tests/testdata/test/doc_success.ts$14-19.jsx ... ok ([WILDCARD]ms) -running 1 test from ./test/doc_success.ts$20-25.ts -[WILDCARD]/tests/testdata/test/doc_success.ts$20-25.ts ... ok ([WILDCARD]ms) -running 1 test from ./test/doc_success.ts$26-31.tsx -[WILDCARD]/tests/testdata/test/doc_success.ts$26-31.tsx ... ok ([WILDCARD]ms) -running 1 test from ./test/doc_success.ts$42-47.ts -[WILDCARD]/tests/testdata/test/doc_success.ts$42-47.ts ... ok ([WILDCARD]ms) - -ok | 5 passed | 0 failed ([WILDCARD]ms) - diff --git a/tests/testdata/test/markdown.out b/tests/testdata/test/markdown.out deleted file mode 100644 index 2a65bc20f60468..00000000000000 --- a/tests/testdata/test/markdown.out +++ /dev/null @@ -1,7 +0,0 @@ -Check [WILDCARD]/test/markdown.md$11-14.js -Check [WILDCARD]/test/markdown.md$17-20.ts -Check [WILDCARD]/test/markdown.md$29-32.ts -error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. - const a: string = 42; - ^ - at [WILDCARD]/test/markdown.md$29-32.ts:2:11 diff --git a/tests/testdata/test/markdown_full_block_names.out b/tests/testdata/test/markdown_full_block_names.out deleted file mode 100644 index b332b3096f6a23..00000000000000 --- a/tests/testdata/test/markdown_full_block_names.out +++ /dev/null @@ -1,6 +0,0 @@ -Check [WILDCARD]/test/markdown_full_block_names.md$5-8.js -Check [WILDCARD]/test/markdown_full_block_names.md$17-20.ts -error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. - const a: string = 42; - ^ - at [WILDCARD]/test/markdown_full_block_names.md$17-20.ts:2:11 diff --git a/tests/testdata/test/markdown_windows.out b/tests/testdata/test/markdown_windows.out deleted file mode 100644 index 8baaf664bbae9e..00000000000000 --- a/tests/testdata/test/markdown_windows.out +++ /dev/null @@ -1,7 +0,0 @@ -Check [WILDCARD]/test/markdown_windows.md$11-14.js -Check [WILDCARD]/test/markdown_windows.md$17-20.ts -Check [WILDCARD]/test/markdown_windows.md$29-32.ts -error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. - const a: string = 42; - ^ - at [WILDCARD]/test/markdown_windows.md$29-32.ts:2:11 diff --git a/tests/testdata/test/markdown_with_comment.out b/tests/testdata/test/markdown_with_comment.out deleted file mode 100644 index a7a7efa11ec0e1..00000000000000 --- a/tests/testdata/test/markdown_with_comment.out +++ /dev/null @@ -1,5 +0,0 @@ -Check [WILDCARD]/test/markdown_with_comment.md$34-37.ts -error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. - const a: string = 42; - ^ - at [WILDCARD]/test/markdown_with_comment.md$34-37.ts:2:11 From f8def1716e4510e34e2a393e682cdc9825071f11 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 27 Aug 2024 01:39:26 +0900 Subject: [PATCH 06/31] update the count --- tools/lint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lint.js b/tools/lint.js index 8c2e0f5949bf16..81be3d361e1932 100755 --- a/tools/lint.js +++ b/tools/lint.js @@ -220,7 +220,7 @@ async function ensureNoNewITests() { "run_tests.rs": 360, "shared_library_tests.rs": 0, "task_tests.rs": 30, - "test_tests.rs": 77, + "test_tests.rs": 71, "upgrade_tests.rs": 0, "vendor_tests.rs": 1, "watcher_tests.rs": 0, From 701f5d96f3d2923189bbd783a59949b9f3e4cfdf Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 27 Aug 2024 01:41:47 +0900 Subject: [PATCH 07/31] remove todo comment --- cli/tools/test/extract.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/tools/test/extract.rs b/cli/tools/test/extract.rs index deecaf3a932e46..bb2c49893186a3 100644 --- a/cli/tools/test/extract.rs +++ b/cli/tools/test/extract.rs @@ -984,5 +984,3 @@ export { default as myDefault } from "./module5.ts"; } } } - -// TODO(magurotuna): add tests for the transformer From 4d9f6f328bfcec5dfbda746f364e142c6766d5a2 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 15:46:02 +0900 Subject: [PATCH 08/31] skip ambient declarations --- cli/tools/test/extract.rs | 73 +++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/cli/tools/test/extract.rs b/cli/tools/test/extract.rs index bb2c49893186a3..2c140004f4b627 100644 --- a/cli/tools/test/extract.rs +++ b/cli/tools/test/extract.rs @@ -254,6 +254,12 @@ impl ExportCollector { } impl Visit for ExportCollector { + fn visit_ts_module_decl(&mut self, ts_module_decl: &ast::TsModuleDecl) { + if ts_module_decl.declare { + return; + } + } + fn visit_export_decl(&mut self, export_decl: &ast::ExportDecl) { match &export_decl.decl { ast::Decl::Class(class) => { @@ -271,14 +277,20 @@ impl Visit for ExportCollector { ast::Decl::TsEnum(ts_enum) => { self.named_exports.push(ts_enum.id.sym.clone()); } - ast::Decl::TsModule(ts_module) => match &ts_module.id { - ast::TsModuleName::Ident(ident) => { - self.named_exports.push(ident.sym.clone()); + ast::Decl::TsModule(ts_module) => { + if ts_module.declare { + return; } - ast::TsModuleName::Str(s) => { - self.named_exports.push(s.value.clone()); + + match &ts_module.id { + ast::TsModuleName::Ident(ident) => { + self.named_exports.push(ident.sym.clone()); + } + ast::TsModuleName::Str(s) => { + self.named_exports.push(s.value.clone()); + } } - }, + } ast::Decl::TsTypeAlias(ts_type_alias) => { self.named_exports.push(ts_type_alias.id.sym.clone()); } @@ -894,6 +906,11 @@ Deno.test("file:///README.md$6-12.js", async ()=>{ named_expected: vec!["foo".into()], default_expected: None, }, + Test { + input: r#"export namespace Foo {}"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, Test { input: r#"export type Foo = string;"#, named_expected: vec!["Foo".into()], @@ -971,6 +988,50 @@ export * as name1 from "./module2.ts"; export { name2, name3 as N3 } from "./module3.js"; export { default } from "./module4.ts"; export { default as myDefault } from "./module5.ts"; +"#, + named_expected: vec![], + default_expected: None, + }, + Test { + input: r#" +export namespace Foo { + export type MyType = string; + export const myValue = 42; + export function myFunc(): boolean; +} +"#, + named_expected: vec!["Foo".into()], + default_expected: None, + }, + Test { + input: r#" +declare namespace Foo { + export type MyType = string; + export const myValue = 42; + export function myFunc(): boolean; +} +"#, + named_expected: vec![], + default_expected: None, + }, + Test { + input: r#" +declare module Foo { + export type MyType = string; + export const myValue = 42; + export function myFunc(): boolean; +} +"#, + named_expected: vec![], + default_expected: None, + }, + Test { + input: r#" +declare global { + export type MyType = string; + export const myValue = 42; + export function myFunc(): boolean; +} "#, named_expected: vec![], default_expected: None, From 56f4c33abcc24fa3f91a199783c9c87cd21a44fb Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 16:27:04 +0900 Subject: [PATCH 09/31] add spec tests --- .../specs/test/doc_ts_declare_global/__test__.jsonc | 5 +++++ tests/specs/test/doc_ts_declare_global/lib.d.ts | 13 +++++++++++++ tests/specs/test/doc_ts_declare_global/lib.d.ts.out | 6 ++++++ tests/specs/test/doc_ts_declare_global/mod.js | 1 + .../specs/test/doc_ts_namespace_decl/__test__.jsonc | 5 +++++ tests/specs/test/doc_ts_namespace_decl/lib.d.ts | 11 +++++++++++ tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out | 6 ++++++ tests/specs/test/doc_ts_namespace_decl/mod.js | 5 +++++ 8 files changed, 52 insertions(+) create mode 100644 tests/specs/test/doc_ts_declare_global/__test__.jsonc create mode 100644 tests/specs/test/doc_ts_declare_global/lib.d.ts create mode 100644 tests/specs/test/doc_ts_declare_global/lib.d.ts.out create mode 100644 tests/specs/test/doc_ts_declare_global/mod.js create mode 100644 tests/specs/test/doc_ts_namespace_decl/__test__.jsonc create mode 100644 tests/specs/test/doc_ts_namespace_decl/lib.d.ts create mode 100644 tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out create mode 100644 tests/specs/test/doc_ts_namespace_decl/mod.js diff --git a/tests/specs/test/doc_ts_declare_global/__test__.jsonc b/tests/specs/test/doc_ts_declare_global/__test__.jsonc new file mode 100644 index 00000000000000..db1e607aadd290 --- /dev/null +++ b/tests/specs/test/doc_ts_declare_global/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --config ../../../config/deno.json lib.d.ts", + "exitCode": 0, + "output": "lib.d.ts.out" +} diff --git a/tests/specs/test/doc_ts_declare_global/lib.d.ts b/tests/specs/test/doc_ts_declare_global/lib.d.ts new file mode 100644 index 00000000000000..a5f442910005b9 --- /dev/null +++ b/tests/specs/test/doc_ts_declare_global/lib.d.ts @@ -0,0 +1,13 @@ +export {}; + +declare global { + /** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * import "./mod.js"; + * + * assertEquals(myFunction(1, 2), 3); + * ``` + */ + export function myFunction(a: number, b: number): number; +} diff --git a/tests/specs/test/doc_ts_declare_global/lib.d.ts.out b/tests/specs/test/doc_ts_declare_global/lib.d.ts.out new file mode 100644 index 00000000000000..88021f61adc071 --- /dev/null +++ b/tests/specs/test/doc_ts_declare_global/lib.d.ts.out @@ -0,0 +1,6 @@ +Check [WILDCARD]/tests/specs/test/doc_ts_declare_global/lib$d$ts$5-11.ts +running 1 test from ./lib$d$ts$5-11.ts +[WILDCARD]/tests/specs/test/doc_ts_declare_global/lib$d$ts$5-11.ts ... ok ([WILDCARD]ms) + +ok | 1 passed | 0 failed ([WILDCARD]ms) + diff --git a/tests/specs/test/doc_ts_declare_global/mod.js b/tests/specs/test/doc_ts_declare_global/mod.js new file mode 100644 index 00000000000000..1b378d2a7a3f92 --- /dev/null +++ b/tests/specs/test/doc_ts_declare_global/mod.js @@ -0,0 +1 @@ +globalThis.myFunction = (a, b) => a + b; diff --git a/tests/specs/test/doc_ts_namespace_decl/__test__.jsonc b/tests/specs/test/doc_ts_namespace_decl/__test__.jsonc new file mode 100644 index 00000000000000..db1e607aadd290 --- /dev/null +++ b/tests/specs/test/doc_ts_namespace_decl/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --config ../../../config/deno.json lib.d.ts", + "exitCode": 0, + "output": "lib.d.ts.out" +} diff --git a/tests/specs/test/doc_ts_namespace_decl/lib.d.ts b/tests/specs/test/doc_ts_namespace_decl/lib.d.ts new file mode 100644 index 00000000000000..e7c81cb5f14477 --- /dev/null +++ b/tests/specs/test/doc_ts_namespace_decl/lib.d.ts @@ -0,0 +1,11 @@ +declare namespace MyNamespace { + /** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * import "./mod.js"; + * + * assertEquals(MyNamespace.add(1, 2), 3); + * ``` + */ + export function add(a: number, b: number): number; +} diff --git a/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out b/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out new file mode 100644 index 00000000000000..c235da376d5cf7 --- /dev/null +++ b/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out @@ -0,0 +1,6 @@ +Check [WILDCARD]/tests/specs/test/doc_ts_namespace_decl/lib$d$ts$3-9.ts +running 1 test from ./lib$d$ts$3-9.ts +[WILDCARD]/tests/specs/test/doc_ts_namespace_decl/lib$d$ts$3-9.ts ... ok ([WILDCARD]ms) + +ok | 1 passed | 0 failed ([WILDCARD]ms) + diff --git a/tests/specs/test/doc_ts_namespace_decl/mod.js b/tests/specs/test/doc_ts_namespace_decl/mod.js new file mode 100644 index 00000000000000..6a96c342fb4d24 --- /dev/null +++ b/tests/specs/test/doc_ts_namespace_decl/mod.js @@ -0,0 +1,5 @@ +globalThis.MyNamespace = { + add(a, b) { + return a + b; + }, +}; From 55a28e803e014538fa6bd523c30fd37df45876b6 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 16:52:21 +0900 Subject: [PATCH 10/31] address duplicate identifier issue --- cli/tools/test/extract.rs | 149 ++++++++++++++++-- .../doc_duplicate_identifier/__test__.jsonc | 5 + .../test/doc_duplicate_identifier/main.out | 11 ++ .../test/doc_duplicate_identifier/main.ts | 29 ++++ .../test/doc_duplicate_identifier/mod.ts | 3 + 5 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 tests/specs/test/doc_duplicate_identifier/__test__.jsonc create mode 100644 tests/specs/test/doc_duplicate_identifier/main.out create mode 100644 tests/specs/test/doc_duplicate_identifier/main.ts create mode 100644 tests/specs/test/doc_duplicate_identifier/mod.ts diff --git a/cli/tools/test/extract.rs b/cli/tools/test/extract.rs index 2c140004f4b627..fe52fc07a4630e 100644 --- a/cli/tools/test/extract.rs +++ b/cli/tools/test/extract.rs @@ -14,6 +14,7 @@ use deno_ast::SourceRangedForSpanned as _; use deno_core::error::AnyError; use deno_core::ModuleSpecifier; use regex::Regex; +use std::collections::HashSet; use std::fmt::Write as _; use std::sync::Arc; @@ -219,22 +220,33 @@ struct ExportCollector { } impl ExportCollector { - fn to_import_specifiers(&self) -> Vec { + fn to_import_specifiers( + &self, + symbols_to_exclude: &HashSet, + ) -> Vec { let mut import_specifiers = vec![]; + if let Some(default_export) = &self.default_export { - import_specifiers.push(ast::ImportSpecifier::Default( - ast::ImportDefaultSpecifier { - span: DUMMY_SP, - local: ast::Ident { + if !symbols_to_exclude.contains(default_export) { + import_specifiers.push(ast::ImportSpecifier::Default( + ast::ImportDefaultSpecifier { span: DUMMY_SP, - ctxt: Default::default(), - sym: default_export.clone(), - optional: false, + local: ast::Ident { + span: DUMMY_SP, + ctxt: Default::default(), + sym: default_export.clone(), + optional: false, + }, }, - }, - )); + )); + } } + for named_export in &self.named_exports { + if symbols_to_exclude.contains(named_export) { + continue; + } + import_specifiers.push(ast::ImportSpecifier::Named( ast::ImportNamedSpecifier { span: DUMMY_SP, @@ -249,6 +261,7 @@ impl ExportCollector { }, )); } + import_specifiers } } @@ -258,6 +271,8 @@ impl Visit for ExportCollector { if ts_module_decl.declare { return; } + + ts_module_decl.visit_children_with(self); } fn visit_export_decl(&mut self, export_decl: &ast::ExportDecl) { @@ -394,6 +409,34 @@ fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { atoms } +#[derive(Default)] +struct ImportedIdentifierCollector { + idents: Vec, +} + +impl Visit for ImportedIdentifierCollector { + fn visit_import_named_specifier( + &mut self, + import_named_specifier: &ast::ImportNamedSpecifier, + ) { + self.idents.push(import_named_specifier.local.clone()); + } + + fn visit_import_default_specifier( + &mut self, + import_default_specifier: &ast::ImportDefaultSpecifier, + ) { + self.idents.push(import_default_specifier.local.clone()); + } + + fn visit_import_star_as_specifier( + &mut self, + import_star_as_specifier: &ast::ImportStarAsSpecifier, + ) { + self.idents.push(import_star_as_specifier.local.clone()); + } +} + /// Generates a "pseudo" test file from a given file by applying the following /// transformations: /// @@ -401,8 +444,9 @@ fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { /// 2. Wraps the content of the file in a `Deno.test` call /// /// For example, given a file that looks like: +/// /// ```ts -/// import { assertEquals } from "@std/assert/equal"; +/// import { assertEquals } from "@std/assert/equals"; /// /// assertEquals(increment(1), 2); /// ``` @@ -420,13 +464,33 @@ fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { /// The generated pseudo test file would look like: /// /// ```ts -/// import { assertEquals } from "@std/assert/equal"; +/// import { assertEquals } from "@std/assert/equals"; /// import { increment, SOME_CONST } from "./base.ts"; /// /// Deno.test("./base.ts$1-3.ts", async () => { /// assertEquals(increment(1), 2); /// }); /// ``` +/// +/// # Edge case - duplicate identifier +/// +/// If a given file imports, say, `doSomething` from an external module while +/// the base file exports `doSomething` as well, the generated pseudo test file +/// would end up having two duplciate imports for `doSomething`, causing the +/// duplicate identifier error. +/// +/// To avoid this issue, when a given file imports `doSomething`, this takes +/// precedence over the automatic import injection for the base file's +/// `doSomething`. So the generated pseudo test file would look like: +/// +/// ```ts +/// import { assertEquals } from "@std/assert/equals"; +/// import { doSomething } from "./some_external_module.ts"; +/// +/// Deno.test("./base.ts$1-3.ts", async () => { +/// assertEquals(doSomething(1), 2); +/// }); +/// ``` fn generate_pseudo_test_file( file: File, base_file_specifier: &ModuleSpecifier, @@ -443,6 +507,9 @@ fn generate_pseudo_test_file( maybe_syntax: None, })?; + let mut import_collector = ImportedIdentifierCollector::default(); + import_collector.visit_program(parsed.program_ref()); + let transformed = parsed .program_ref() @@ -450,7 +517,12 @@ fn generate_pseudo_test_file( .fold_with(&mut as_folder(Transform { specifier: &file.specifier, base_file_specifier, - exports, + exports_from_base: exports, + explicit_imports: import_collector + .idents + .into_iter() + .map(|i| i.sym) + .collect(), })); Ok(File { @@ -465,7 +537,8 @@ fn generate_pseudo_test_file( struct Transform<'a> { specifier: &'a ModuleSpecifier, base_file_specifier: &'a ModuleSpecifier, - exports: &'a ExportCollector, + exports_from_base: &'a ExportCollector, + explicit_imports: HashSet, } impl<'a> VisitMut for Transform<'a> { @@ -489,7 +562,9 @@ impl<'a> VisitMut for Transform<'a> { let mut transformed_items = vec![]; transformed_items .extend(module_decls.into_iter().map(ast::ModuleItem::ModuleDecl)); - let import_specifiers = self.exports.to_import_specifiers(); + let import_specifiers = self + .exports_from_base + .to_import_specifiers(&self.explicit_imports); if !import_specifiers.is_empty() { transformed_items.push(ast::ModuleItem::ModuleDecl( ast::ModuleDecl::Import(ast::ImportDecl { @@ -516,12 +591,16 @@ impl<'a> VisitMut for Transform<'a> { ast::Program::Script(script) => { let mut transformed_items = vec![]; - let import_specifiers = self.exports.to_import_specifiers(); + let import_specifiers = self + .exports_from_base + .to_import_specifiers(&self.explicit_imports); if !import_specifiers.is_empty() { transformed_items.push(ast::ModuleItem::ModuleDecl( ast::ModuleDecl::Import(ast::ImportDecl { span: DUMMY_SP, - specifiers: self.exports.to_import_specifiers(), + specifiers: self + .exports_from_base + .to_import_specifiers(&self.explicit_imports), src: Box::new(ast::Str { span: DUMMY_SP, value: self.base_file_specifier.to_string().into(), @@ -783,6 +862,42 @@ Deno.test("file:///main.ts$13-16.ts", async ()=>{ }, ], }, + // Avoid duplicate imports + Test { + input: Input { + source: r#" +/** + * ```ts + * import { DUPLICATE1 } from "./other1.ts"; + * import * as DUPLICATE2 from "./other2.js"; + * import { foo as DUPLICATE3 } from "./other3.tsx"; + * + * foo(); + * ``` + */ +export function foo() {} + +export const DUPLICATE1 = "dup1"; +const DUPLICATE2 = "dup2"; +export default DUPLICATE2; +const DUPLICATE3 = "dup3"; +export { DUPLICATE3 }; +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { DUPLICATE1 } from "./other1.ts"; +import * as DUPLICATE2 from "./other2.js"; +import { foo as DUPLICATE3 } from "./other3.tsx"; +import { foo } from "file:///main.ts"; +Deno.test("file:///main.ts$3-10.ts", async ()=>{ + foo(); +}); +"#, + specifier: "file:///main.ts$3-10.ts", + media_type: MediaType::TypeScript, + }], + }, Test { input: Input { source: r#" diff --git a/tests/specs/test/doc_duplicate_identifier/__test__.jsonc b/tests/specs/test/doc_duplicate_identifier/__test__.jsonc new file mode 100644 index 00000000000000..2a8e6aafca60b5 --- /dev/null +++ b/tests/specs/test/doc_duplicate_identifier/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --config ../../../config/deno.json main.ts", + "exitCode": 0, + "output": "main.out" +} diff --git a/tests/specs/test/doc_duplicate_identifier/main.out b/tests/specs/test/doc_duplicate_identifier/main.out new file mode 100644 index 00000000000000..db3e67d93b89f2 --- /dev/null +++ b/tests/specs/test/doc_duplicate_identifier/main.out @@ -0,0 +1,11 @@ +Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts +Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$10-17.ts +Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$23-28.ts +running 0 tests from ./main.ts +running 1 test from ./main.ts$10-17.ts +[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$10-17.ts ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$23-28.ts +[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$23-28.ts ... ok ([WILDCARD]ms) + +ok | 2 passed | 0 failed ([WILDCARD]ms) + diff --git a/tests/specs/test/doc_duplicate_identifier/main.ts b/tests/specs/test/doc_duplicate_identifier/main.ts new file mode 100644 index 00000000000000..a36f18d239035c --- /dev/null +++ b/tests/specs/test/doc_duplicate_identifier/main.ts @@ -0,0 +1,29 @@ +// `deno test --doc` tries to convert the example code snippets into pseudo +// test files in a way that all the exported items are available without +// explicit import statements. Therefore, in the test code, you don't have to +// write like `import { add } from "./main.ts";`. +// However, this automatic import resolution might conflict with other explicit +// imports in the test code you write. This spec test exists to make sure that +// such cases will not cause any issues - explicit imports take precedence. + +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * import { getModuleName } from "./mod.ts"; + * + * assertEquals(getModuleName(), "mod.ts"); + * assertEquals(add(1, 2), 3); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} + +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(getModuleName(), "main.ts"); + * ``` + */ +export const getModuleName = () => "main.ts"; diff --git a/tests/specs/test/doc_duplicate_identifier/mod.ts b/tests/specs/test/doc_duplicate_identifier/mod.ts new file mode 100644 index 00000000000000..efbfed9335086a --- /dev/null +++ b/tests/specs/test/doc_duplicate_identifier/mod.ts @@ -0,0 +1,3 @@ +export function getModuleName() { + return "mod.ts"; +} From 59dcc59e85bb7ed4d72ed354aef92e91894b4c4b Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 20:10:11 +0900 Subject: [PATCH 11/31] fix help message of --doc option --- cli/args/flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index ca8a0a82fa6275..3b4661054ff45b 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -2894,7 +2894,7 @@ Directory arguments are expanded to all contained files matching the glob .arg( Arg::new("doc") .long("doc") - .help("Type-check code blocks in JSDoc and Markdown") + .help("Evaluate code blocks in JSDoc and Markdown") .action(ArgAction::SetTrue) .help_heading(TEST_HEADING), ) From 5147611783c91d88abf052332a8eba46f5e692c3 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 20:08:40 +0900 Subject: [PATCH 12/31] move extract logic to crate root --- cli/{tools/test => }/extract.rs | 198 +++++++++++++++++++++++++++++--- cli/main.rs | 1 + cli/tools/test/mod.rs | 6 +- 3 files changed, 186 insertions(+), 19 deletions(-) rename cli/{tools/test => }/extract.rs (85%) diff --git a/cli/tools/test/extract.rs b/cli/extract.rs similarity index 85% rename from cli/tools/test/extract.rs rename to cli/extract.rs index fe52fc07a4630e..05891e6c7c46f1 100644 --- a/cli/tools/test/extract.rs +++ b/cli/extract.rs @@ -24,7 +24,26 @@ use crate::util::path::mapped_specifier_for_tsc; /// Extracts doc tests from a given file, transforms them into pseudo test /// files by wrapping the content of the doc tests in a `Deno.test` call, and /// returns a list of the pseudo test files. -pub(super) fn extract_doc_tests(file: File) -> Result, AnyError> { +/// +/// The difference from [`extract_snippet_files`] is that this function wraps +/// extracted code snippets in a `Deno.test` call. +pub fn extract_doc_tests(file: File) -> Result, AnyError> { + extract_inner(file, true) +} + +/// Extracts code snippets from a given file and returns a list of the extracted +/// files. +/// +/// The difference from [`extract_doc_tests`] is that this function does *not* +/// wrap extracted code snippets in a `Deno.test` call. +pub fn extract_snippet_files(file: File) -> Result, AnyError> { + extract_inner(file, false) +} + +fn extract_inner( + file: File, + wrap_in_deno_test: bool, +) -> Result, AnyError> { let file = file.into_text_decoded()?; let exports = match deno_ast::parse_program(deno_ast::ParseParams { @@ -60,7 +79,12 @@ pub(super) fn extract_doc_tests(file: File) -> Result, AnyError> { extracted_files .into_iter() .map(|extracted_file| { - generate_pseudo_test_file(extracted_file, &file.specifier, &exports) + generate_pseudo_file( + extracted_file, + &file.specifier, + &exports, + wrap_in_deno_test, + ) }) .collect::>() } @@ -437,11 +461,11 @@ impl Visit for ImportedIdentifierCollector { } } -/// Generates a "pseudo" test file from a given file by applying the following +/// Generates a "pseudo" file from a given file by applying the following /// transformations: /// /// 1. Injects `import` statements for expoted items from the base file -/// 2. Wraps the content of the file in a `Deno.test` call +/// 2. Wraps the content of the file in a `Deno.test` call (if `wrap_in_deno_test` is `true`) /// /// For example, given a file that looks like: /// @@ -455,20 +479,20 @@ impl Visit for ImportedIdentifierCollector { /// /// ```ts /// export function increment(n: number): number { -/// return n + 1; +/// return n + 1; /// } /// /// export const SOME_CONST = "HELLO"; /// ``` /// -/// The generated pseudo test file would look like: +/// The generated pseudo test file would look like (if `wrap_in_deno_test` is enabled): /// /// ```ts /// import { assertEquals } from "@std/assert/equals"; /// import { increment, SOME_CONST } from "./base.ts"; /// /// Deno.test("./base.ts$1-3.ts", async () => { -/// assertEquals(increment(1), 2); +/// assertEquals(increment(1), 2); /// }); /// ``` /// @@ -491,10 +515,11 @@ impl Visit for ImportedIdentifierCollector { /// assertEquals(doSomething(1), 2); /// }); /// ``` -fn generate_pseudo_test_file( +fn generate_pseudo_file( file: File, base_file_specifier: &ModuleSpecifier, exports: &ExportCollector, + wrap_in_deno_test: bool, ) -> Result { let file = file.into_text_decoded()?; @@ -523,6 +548,7 @@ fn generate_pseudo_test_file( .into_iter() .map(|i| i.sym) .collect(), + wrap_in_deno_test, })); Ok(File { @@ -539,6 +565,7 @@ struct Transform<'a> { base_file_specifier: &'a ModuleSpecifier, exports_from_base: &'a ExportCollector, explicit_imports: HashSet, + wrap_in_deno_test: bool, } impl<'a> VisitMut for Transform<'a> { @@ -581,10 +608,15 @@ impl<'a> VisitMut for Transform<'a> { }), )); } - transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( - stmts, - self.specifier.to_string().into(), - ))); + if self.wrap_in_deno_test { + transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( + stmts, + self.specifier.to_string().into(), + ))); + } else { + transformed_items + .extend(stmts.into_iter().map(ast::ModuleItem::Stmt)); + } transformed_items } @@ -613,10 +645,15 @@ impl<'a> VisitMut for Transform<'a> { )); } - transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( - script.body.clone(), - self.specifier.to_string().into(), - ))); + if self.wrap_in_deno_test { + transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( + script.body.clone(), + self.specifier.to_string().into(), + ))); + } else { + transformed_items + .extend(script.body.clone().into_iter().map(ast::ModuleItem::Stmt)); + } transformed_items } @@ -951,6 +988,135 @@ Deno.test("file:///README.md$6-12.js", async ()=>{ } } + #[test] + fn test_extract_snippet_files() { + struct Input { + source: &'static str, + specifier: &'static str, + } + struct Expected { + source: &'static str, + specifier: &'static str, + media_type: MediaType, + } + struct Test { + input: Input, + expected: Vec, + } + + let tests = [ + Test { + input: Input { + source: r#""#, + specifier: "file:///main.ts", + }, + expected: vec![], + }, + Test { + input: Input { + source: r#" +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * assertEquals(add(1, 2), 3); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { assertEquals } from "@std/assert/equals"; +import { add } from "file:///main.ts"; +assertEquals(add(1, 2), 3); +"#, + specifier: "file:///main.ts$3-8.ts", + media_type: MediaType::TypeScript, + }], + }, + Test { + input: Input { + source: r#" +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * import { DUPLICATE } from "./other.ts"; + * + * assertEquals(add(1, 2), 3); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} + +export const DUPLICATE = "dup"; +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { assertEquals } from "@std/assert/equals"; +import { DUPLICATE } from "./other.ts"; +import { add } from "file:///main.ts"; +assertEquals(add(1, 2), 3); +"#, + specifier: "file:///main.ts$3-9.ts", + media_type: MediaType::TypeScript, + }], + }, + Test { + input: Input { + source: r#" +# Header + +This is a *markdown*. + +```js +import { assertEquals } from "@std/assert/equal"; +import { add } from "jsr:@deno/non-existent"; + +assertEquals(add(1, 2), 3); +``` +"#, + specifier: "file:///README.md", + }, + expected: vec![Expected { + source: r#"import { assertEquals } from "@std/assert/equal"; +import { add } from "jsr:@deno/non-existent"; +assertEquals(add(1, 2), 3); +"#, + specifier: "file:///README.md$6-12.js", + media_type: MediaType::JavaScript, + }], + }, + ]; + + for test in tests { + let file = File { + specifier: ModuleSpecifier::parse(test.input.specifier).unwrap(), + maybe_headers: None, + source: test.input.source.as_bytes().into(), + }; + let got_decoded = extract_snippet_files(file) + .unwrap() + .into_iter() + .map(|f| f.into_text_decoded().unwrap()) + .collect::>(); + let expected = test + .expected + .iter() + .map(|e| TextDecodedFile { + specifier: ModuleSpecifier::parse(e.specifier).unwrap(), + media_type: e.media_type, + source: e.source.into(), + }) + .collect::>(); + assert_eq!(got_decoded, expected); + } + } + #[test] fn test_export_collector() { fn helper(input: &'static str) -> ExportCollector { diff --git a/cli/main.rs b/cli/main.rs index ee2bd79cc28a8a..0fdd2cb8b90a50 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -6,6 +6,7 @@ mod cache; mod cdp; mod emit; mod errors; +mod extract; mod factory; mod file_fetcher; mod graph_container; diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 3a7540c65dbdaa..905454264316de 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -6,6 +6,7 @@ use crate::args::TestFlags; use crate::args::TestReporterConfig; use crate::colors; use crate::display; +use crate::extract::extract_doc_tests; use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::graph_util::has_graph_root_local_dependent_changed; @@ -84,7 +85,6 @@ use std::time::Instant; use tokio::signal; mod channel; -mod extract; pub mod fmt; pub mod reporters; @@ -1583,7 +1583,7 @@ pub async fn run_tests( let file = file_fetcher .fetch(s, &PermissionsContainer::allow_all()) .await?; - let doc_tests = extract::extract_doc_tests(file)?; + let doc_tests = extract_doc_tests(file)?; for doc_test in doc_tests { final_specifiers.push(doc_test.specifier.clone()); file_fetcher.insert_memory_files(doc_test); @@ -1774,7 +1774,7 @@ pub async fn run_tests_with_watch( let file = file_fetcher .fetch(s, &PermissionsContainer::allow_all()) .await?; - let doc_tests = extract::extract_doc_tests(file)?; + let doc_tests = extract_doc_tests(file)?; for doc_test in doc_tests { final_specifiers.push(doc_test.specifier.clone()); file_fetcher.insert_memory_files(doc_test); From b872b8cbcdf649b7eaedd022971ca5daec10ed56 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 20:44:23 +0900 Subject: [PATCH 13/31] deal with overload correctly --- cli/extract.rs | 92 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/cli/extract.rs b/cli/extract.rs index 05891e6c7c46f1..258c50b8462734 100644 --- a/cli/extract.rs +++ b/cli/extract.rs @@ -239,7 +239,7 @@ fn extract_files_from_regex_blocks( #[derive(Default)] struct ExportCollector { - named_exports: Vec, + named_exports: HashSet, default_export: Option, } @@ -302,10 +302,10 @@ impl Visit for ExportCollector { fn visit_export_decl(&mut self, export_decl: &ast::ExportDecl) { match &export_decl.decl { ast::Decl::Class(class) => { - self.named_exports.push(class.ident.sym.clone()); + self.named_exports.insert(class.ident.sym.clone()); } ast::Decl::Fn(func) => { - self.named_exports.push(func.ident.sym.clone()); + self.named_exports.insert(func.ident.sym.clone()); } ast::Decl::Var(var) => { for var_decl in &var.decls { @@ -314,7 +314,7 @@ impl Visit for ExportCollector { } } ast::Decl::TsEnum(ts_enum) => { - self.named_exports.push(ts_enum.id.sym.clone()); + self.named_exports.insert(ts_enum.id.sym.clone()); } ast::Decl::TsModule(ts_module) => { if ts_module.declare { @@ -323,18 +323,18 @@ impl Visit for ExportCollector { match &ts_module.id { ast::TsModuleName::Ident(ident) => { - self.named_exports.push(ident.sym.clone()); + self.named_exports.insert(ident.sym.clone()); } ast::TsModuleName::Str(s) => { - self.named_exports.push(s.value.clone()); + self.named_exports.insert(s.value.clone()); } } } ast::Decl::TsTypeAlias(ts_type_alias) => { - self.named_exports.push(ts_type_alias.id.sym.clone()); + self.named_exports.insert(ts_type_alias.id.sym.clone()); } ast::Decl::TsInterface(ts_interface) => { - self.named_exports.push(ts_interface.id.sym.clone()); + self.named_exports.insert(ts_interface.id.sym.clone()); } ast::Decl::Using(_) => {} } @@ -372,12 +372,12 @@ impl Visit for ExportCollector { match &export_named_specifier.exported { Some(exported) => { - self.named_exports.push(get_atom(exported)); + self.named_exports.insert(get_atom(exported)); } None => { self .named_exports - .push(get_atom(&export_named_specifier.orig)); + .insert(get_atom(&export_named_specifier.orig)); } } } @@ -1137,109 +1137,115 @@ assertEquals(add(1, 2), 3); struct Test { input: &'static str, - named_expected: Vec, + named_expected: HashSet, default_expected: Option, } + macro_rules! atom_set { + ($( $x:expr ),*) => { + [$( Atom::from($x) ),*].into_iter().collect::>() + }; + } + let tests = [ Test { input: r#"export const foo = 42;"#, - named_expected: vec!["foo".into()], + named_expected: atom_set!("foo"), default_expected: None, }, Test { input: r#"export let foo = 42;"#, - named_expected: vec!["foo".into()], + named_expected: atom_set!("foo"), default_expected: None, }, Test { input: r#"export var foo = 42;"#, - named_expected: vec!["foo".into()], + named_expected: atom_set!("foo"), default_expected: None, }, Test { input: r#"export const foo = () => {};"#, - named_expected: vec!["foo".into()], + named_expected: atom_set!("foo"), default_expected: None, }, Test { input: r#"export function foo() {}"#, - named_expected: vec!["foo".into()], + named_expected: atom_set!("foo"), default_expected: None, }, Test { input: r#"export class Foo {}"#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { input: r#"export enum Foo {}"#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { input: r#"export module Foo {}"#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { input: r#"export module "foo" {}"#, - named_expected: vec!["foo".into()], + named_expected: atom_set!("foo"), default_expected: None, }, Test { input: r#"export namespace Foo {}"#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { input: r#"export type Foo = string;"#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { input: r#"export interface Foo {};"#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { input: r#"export let name1, name2;"#, - named_expected: vec!["name1".into(), "name2".into()], + named_expected: atom_set!("name1", "name2"), default_expected: None, }, Test { input: r#"export const name1 = 1, name2 = 2;"#, - named_expected: vec!["name1".into(), "name2".into()], + named_expected: atom_set!("name1", "name2"), default_expected: None, }, Test { input: r#"export function* generatorFunc() {}"#, - named_expected: vec!["generatorFunc".into()], + named_expected: atom_set!("generatorFunc"), default_expected: None, }, Test { input: r#"export const { name1, name2: bar } = obj;"#, - named_expected: vec!["name1".into(), "bar".into()], + named_expected: atom_set!("name1", "bar"), default_expected: None, }, Test { input: r#"export const [name1, name2] = arr;"#, - named_expected: vec!["name1".into(), "name2".into()], + named_expected: atom_set!("name1", "name2"), default_expected: None, }, Test { input: r#"export const { name1 = 42 } = arr;"#, - named_expected: vec!["name1".into()], + named_expected: atom_set!("name1"), default_expected: None, }, Test { input: r#"export default function foo() {}"#, - named_expected: vec![], + named_expected: atom_set!(), default_expected: Some("foo".into()), }, Test { input: r#"export { foo, bar as barAlias };"#, - named_expected: vec!["foo".into(), "barAlias".into()], + named_expected: atom_set!("foo", "barAlias"), default_expected: None, }, Test { @@ -1254,9 +1260,21 @@ const value3 = "World"; export { value2 }; "#, - named_expected: vec!["value1".into(), "value2".into()], + named_expected: atom_set!("value1", "value2"), default_expected: Some("Foo".into()), }, + // overloaded function + Test { + input: r#" +export function foo(a: number): boolean; +export function foo(a: boolean): string; +export function foo(a: number | boolean): boolean | string { + return typeof a === "number" ? true : "hello"; +} +"#, + named_expected: atom_set!("foo"), + default_expected: None, + }, // The collector deliberately does not handle re-exports, because from // doc reader's perspective, an example code would become hard to follow // if it uses re-exported items (as opposed to normal, non-re-exported @@ -1270,7 +1288,7 @@ export { name2, name3 as N3 } from "./module3.js"; export { default } from "./module4.ts"; export { default as myDefault } from "./module5.ts"; "#, - named_expected: vec![], + named_expected: atom_set!(), default_expected: None, }, Test { @@ -1281,7 +1299,7 @@ export namespace Foo { export function myFunc(): boolean; } "#, - named_expected: vec!["Foo".into()], + named_expected: atom_set!("Foo"), default_expected: None, }, Test { @@ -1292,7 +1310,7 @@ declare namespace Foo { export function myFunc(): boolean; } "#, - named_expected: vec![], + named_expected: atom_set!(), default_expected: None, }, Test { @@ -1303,7 +1321,7 @@ declare module Foo { export function myFunc(): boolean; } "#, - named_expected: vec![], + named_expected: atom_set!(), default_expected: None, }, Test { @@ -1314,7 +1332,7 @@ declare global { export function myFunc(): boolean; } "#, - named_expected: vec![], + named_expected: atom_set!(), default_expected: None, }, ]; From 288ab935aa38758dbf178388be2761ca03b3efa1 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Thu, 29 Aug 2024 23:07:33 +0900 Subject: [PATCH 14/31] handle top-level defined vars --- cli/extract.rs | 177 ++++++++++++------ .../test/doc_duplicate_identifier/main.out | 12 +- .../test/doc_duplicate_identifier/main.ts | 14 +- .../test/doc_duplicate_identifier/mod.ts | 4 + 4 files changed, 140 insertions(+), 67 deletions(-) diff --git a/cli/extract.rs b/cli/extract.rs index 258c50b8462734..d0322feaaef555 100644 --- a/cli/extract.rs +++ b/cli/extract.rs @@ -2,8 +2,10 @@ use deno_ast::swc::ast; use deno_ast::swc::atoms::Atom; +use deno_ast::swc::common::collections::AHashSet; use deno_ast::swc::common::comments::CommentKind; use deno_ast::swc::common::DUMMY_SP; +use deno_ast::swc::utils as swc_utils; use deno_ast::swc::visit::as_folder; use deno_ast::swc::visit::FoldWith as _; use deno_ast::swc::visit::Visit; @@ -14,7 +16,7 @@ use deno_ast::SourceRangedForSpanned as _; use deno_core::error::AnyError; use deno_core::ModuleSpecifier; use regex::Regex; -use std::collections::HashSet; +use std::collections::BTreeSet; use std::fmt::Write as _; use std::sync::Arc; @@ -28,7 +30,7 @@ use crate::util::path::mapped_specifier_for_tsc; /// The difference from [`extract_snippet_files`] is that this function wraps /// extracted code snippets in a `Deno.test` call. pub fn extract_doc_tests(file: File) -> Result, AnyError> { - extract_inner(file, true) + extract_inner(file, WrapKind::DenoTest) } /// Extracts code snippets from a given file and returns a list of the extracted @@ -37,12 +39,18 @@ pub fn extract_doc_tests(file: File) -> Result, AnyError> { /// The difference from [`extract_doc_tests`] is that this function does *not* /// wrap extracted code snippets in a `Deno.test` call. pub fn extract_snippet_files(file: File) -> Result, AnyError> { - extract_inner(file, false) + extract_inner(file, WrapKind::NoWrap) +} + +#[derive(Clone, Copy)] +enum WrapKind { + DenoTest, + NoWrap, } fn extract_inner( file: File, - wrap_in_deno_test: bool, + wrap_kind: WrapKind, ) -> Result, AnyError> { let file = file.into_text_decoded()?; @@ -79,12 +87,7 @@ fn extract_inner( extracted_files .into_iter() .map(|extracted_file| { - generate_pseudo_file( - extracted_file, - &file.specifier, - &exports, - wrap_in_deno_test, - ) + generate_pseudo_file(extracted_file, &file.specifier, &exports, wrap_kind) }) .collect::>() } @@ -239,14 +242,14 @@ fn extract_files_from_regex_blocks( #[derive(Default)] struct ExportCollector { - named_exports: HashSet, + named_exports: BTreeSet, default_export: Option, } impl ExportCollector { fn to_import_specifiers( &self, - symbols_to_exclude: &HashSet, + symbols_to_exclude: &AHashSet, ) -> Vec { let mut import_specifiers = vec![]; @@ -465,7 +468,8 @@ impl Visit for ImportedIdentifierCollector { /// transformations: /// /// 1. Injects `import` statements for expoted items from the base file -/// 2. Wraps the content of the file in a `Deno.test` call (if `wrap_in_deno_test` is `true`) +/// 2. If `wrap_kind` is [`WrapKind::DenoTest`], wraps the content of the file +/// in a `Deno.test` call. /// /// For example, given a file that looks like: /// @@ -519,7 +523,7 @@ fn generate_pseudo_file( file: File, base_file_specifier: &ModuleSpecifier, exports: &ExportCollector, - wrap_in_deno_test: bool, + wrap_kind: WrapKind, ) -> Result { let file = file.into_text_decoded()?; @@ -528,12 +532,14 @@ fn generate_pseudo_file( text: file.source, media_type: file.media_type, capture_tokens: false, - scope_analysis: false, + scope_analysis: true, maybe_syntax: None, })?; - let mut import_collector = ImportedIdentifierCollector::default(); - import_collector.visit_program(parsed.program_ref()); + let top_level_atoms = swc_utils::collect_decls_with_ctxt::( + parsed.program_ref(), + parsed.top_level_context(), + ); let transformed = parsed @@ -543,20 +549,18 @@ fn generate_pseudo_file( specifier: &file.specifier, base_file_specifier, exports_from_base: exports, - explicit_imports: import_collector - .idents - .into_iter() - .map(|i| i.sym) - .collect(), - wrap_in_deno_test, + atoms_to_be_excluded_from_import: top_level_atoms, + wrap_kind, })); + let source = deno_ast::swc::codegen::to_code(&transformed); + + log::debug!("{}:\n{}", file.specifier, source); + Ok(File { specifier: file.specifier, maybe_headers: None, - source: deno_ast::swc::codegen::to_code(&transformed) - .into_bytes() - .into(), + source: source.into_bytes().into(), }) } @@ -564,8 +568,8 @@ struct Transform<'a> { specifier: &'a ModuleSpecifier, base_file_specifier: &'a ModuleSpecifier, exports_from_base: &'a ExportCollector, - explicit_imports: HashSet, - wrap_in_deno_test: bool, + atoms_to_be_excluded_from_import: AHashSet, + wrap_kind: WrapKind, } impl<'a> VisitMut for Transform<'a> { @@ -591,7 +595,7 @@ impl<'a> VisitMut for Transform<'a> { .extend(module_decls.into_iter().map(ast::ModuleItem::ModuleDecl)); let import_specifiers = self .exports_from_base - .to_import_specifiers(&self.explicit_imports); + .to_import_specifiers(&self.atoms_to_be_excluded_from_import); if !import_specifiers.is_empty() { transformed_items.push(ast::ModuleItem::ModuleDecl( ast::ModuleDecl::Import(ast::ImportDecl { @@ -608,14 +612,17 @@ impl<'a> VisitMut for Transform<'a> { }), )); } - if self.wrap_in_deno_test { - transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( - stmts, - self.specifier.to_string().into(), - ))); - } else { - transformed_items - .extend(stmts.into_iter().map(ast::ModuleItem::Stmt)); + match self.wrap_kind { + WrapKind::DenoTest => { + transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( + stmts, + self.specifier.to_string().into(), + ))); + } + WrapKind::NoWrap => { + transformed_items + .extend(stmts.into_iter().map(ast::ModuleItem::Stmt)); + } } transformed_items @@ -625,14 +632,12 @@ impl<'a> VisitMut for Transform<'a> { let import_specifiers = self .exports_from_base - .to_import_specifiers(&self.explicit_imports); + .to_import_specifiers(&self.atoms_to_be_excluded_from_import); if !import_specifiers.is_empty() { transformed_items.push(ast::ModuleItem::ModuleDecl( ast::ModuleDecl::Import(ast::ImportDecl { span: DUMMY_SP, - specifiers: self - .exports_from_base - .to_import_specifiers(&self.explicit_imports), + specifiers: import_specifiers, src: Box::new(ast::Str { span: DUMMY_SP, value: self.base_file_specifier.to_string().into(), @@ -645,14 +650,18 @@ impl<'a> VisitMut for Transform<'a> { )); } - if self.wrap_in_deno_test { - transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( - script.body.clone(), - self.specifier.to_string().into(), - ))); - } else { - transformed_items - .extend(script.body.clone().into_iter().map(ast::ModuleItem::Stmt)); + match self.wrap_kind { + WrapKind::DenoTest => { + transformed_items.push(ast::ModuleItem::Stmt(wrap_in_deno_test( + script.body.clone(), + self.specifier.to_string().into(), + ))); + } + WrapKind::NoWrap => { + transformed_items.extend( + script.body.clone().into_iter().map(ast::ModuleItem::Stmt), + ); + } } transformed_items @@ -816,7 +825,7 @@ export type Args = { a: number }; specifier: "file:///main.ts", }, expected: vec![Expected { - source: r#"import { foo, Args } from "file:///main.ts"; + source: r#"import { Args, foo } from "file:///main.ts"; Deno.test("file:///main.ts$3-7.ts", async ()=>{ const input = { a: 42 @@ -935,6 +944,35 @@ Deno.test("file:///main.ts$3-10.ts", async ()=>{ media_type: MediaType::TypeScript, }], }, + // duplication of imported identifier and local identifier is fine + Test { + input: Input { + source: r#" +/** + * ```ts + * const foo = createFoo(); + * foo(); + * ``` + */ +export function createFoo() { + return () => "created foo"; +} + +export const foo = () => "foo"; +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { createFoo } from "file:///main.ts"; +Deno.test("file:///main.ts$3-7.ts", async ()=>{ + const foo = createFoo(); + foo(); +}); +"#, + specifier: "file:///main.ts$3-7.ts", + media_type: MediaType::TypeScript, + }], + }, Test { input: Input { source: r#" @@ -1066,6 +1104,37 @@ assertEquals(add(1, 2), 3); media_type: MediaType::TypeScript, }], }, + // duplication of imported identifier and local identifier is fine, since + // we wrap the snippet in a block. + // This would be a problem if the local one is declared with `var`, as + // `var` is not block scoped but function scoped. For now we don't handle + // this case assuming that `var` is not used in modern code. + Test { + input: Input { + source: r#" + /** + * ```ts + * const foo = createFoo(); + * foo(); + * ``` + */ + export function createFoo() { + return () => "created foo"; + } + + export const foo = () => "foo"; + "#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"import { createFoo } from "file:///main.ts"; +const foo = createFoo(); +foo(); +"#, + specifier: "file:///main.ts$3-7.ts", + media_type: MediaType::TypeScript, + }], + }, Test { input: Input { source: r#" @@ -1137,13 +1206,13 @@ assertEquals(add(1, 2), 3); struct Test { input: &'static str, - named_expected: HashSet, + named_expected: BTreeSet, default_expected: Option, } macro_rules! atom_set { ($( $x:expr ),*) => { - [$( Atom::from($x) ),*].into_iter().collect::>() + [$( Atom::from($x) ),*].into_iter().collect::>() }; } @@ -1251,13 +1320,9 @@ assertEquals(add(1, 2), 3); Test { input: r#" export default class Foo {} - export let value1 = 42; - const value2 = "Hello"; - const value3 = "World"; - export { value2 }; "#, named_expected: atom_set!("value1", "value2"), diff --git a/tests/specs/test/doc_duplicate_identifier/main.out b/tests/specs/test/doc_duplicate_identifier/main.out index db3e67d93b89f2..ffcec773ae1bec 100644 --- a/tests/specs/test/doc_duplicate_identifier/main.out +++ b/tests/specs/test/doc_duplicate_identifier/main.out @@ -1,11 +1,11 @@ Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts -Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$10-17.ts -Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$23-28.ts +Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$11-19.ts +Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$25-30.ts running 0 tests from ./main.ts -running 1 test from ./main.ts$10-17.ts -[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$10-17.ts ... ok ([WILDCARD]ms) -running 1 test from ./main.ts$23-28.ts -[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$23-28.ts ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$11-19.ts +[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$11-19.ts ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$25-30.ts +[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$25-30.ts ... ok ([WILDCARD]ms) ok | 2 passed | 0 failed ([WILDCARD]ms) diff --git a/tests/specs/test/doc_duplicate_identifier/main.ts b/tests/specs/test/doc_duplicate_identifier/main.ts index a36f18d239035c..df78294d0ac078 100644 --- a/tests/specs/test/doc_duplicate_identifier/main.ts +++ b/tests/specs/test/doc_duplicate_identifier/main.ts @@ -2,17 +2,19 @@ // test files in a way that all the exported items are available without // explicit import statements. Therefore, in the test code, you don't have to // write like `import { add } from "./main.ts";`. -// However, this automatic import resolution might conflict with other explicit -// imports in the test code you write. This spec test exists to make sure that -// such cases will not cause any issues - explicit imports take precedence. +// However, this automatic import resolution might conflict with other +// explicitly declared identifiers in the test code you write. This spec test +// makes sure that such cases will not cause any issues - explicit identifiers +// take precedence. /** * ```ts * import { assertEquals } from "@std/assert/equals"; - * import { getModuleName } from "./mod.ts"; + * import { getModuleName, createFoo } from "./mod.ts"; * + * const foo = createFoo(); * assertEquals(getModuleName(), "mod.ts"); - * assertEquals(add(1, 2), 3); + * assertEquals(add(1, 2), foo()); * ``` */ export function add(a: number, b: number): number { @@ -27,3 +29,5 @@ export function add(a: number, b: number): number { * ``` */ export const getModuleName = () => "main.ts"; + +export let foo = 1234; diff --git a/tests/specs/test/doc_duplicate_identifier/mod.ts b/tests/specs/test/doc_duplicate_identifier/mod.ts index efbfed9335086a..c613a99adf16e4 100644 --- a/tests/specs/test/doc_duplicate_identifier/mod.ts +++ b/tests/specs/test/doc_duplicate_identifier/mod.ts @@ -1,3 +1,7 @@ export function getModuleName() { return "mod.ts"; } + +export const createFoo = () => { + return () => 3; +}; From 3795d8c573aea84dcf1593d733957aa03b40c1e9 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Wed, 28 Aug 2024 18:27:17 +0900 Subject: [PATCH 15/31] add --doc option to check command --- cli/args/flags.rs | 27 ++++++++- cli/main.rs | 7 +-- cli/tools/check.rs | 55 +++++++++++++++++++ tests/integration/check_tests.rs | 5 +- .../typecheck_doc_failure/__test__.jsonc | 5 ++ .../specs/check/typecheck_doc_failure/mod.out | 6 ++ .../specs/check/typecheck_doc_failure/mod.ts | 8 +++ .../typecheck_doc_in_markdown/__test__.jsonc | 5 ++ .../typecheck_doc_in_markdown/markdown.md | 31 +++++++++++ .../typecheck_doc_in_markdown/markdown.out | 7 +++ .../typecheck_doc_success/__test__.jsonc | 5 ++ .../specs/check/typecheck_doc_success/mod.out | 2 + .../specs/check/typecheck_doc_success/mod.ts | 12 ++++ 13 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 tests/specs/check/typecheck_doc_failure/__test__.jsonc create mode 100644 tests/specs/check/typecheck_doc_failure/mod.out create mode 100644 tests/specs/check/typecheck_doc_failure/mod.ts create mode 100644 tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc create mode 100644 tests/specs/check/typecheck_doc_in_markdown/markdown.md create mode 100644 tests/specs/check/typecheck_doc_in_markdown/markdown.out create mode 100644 tests/specs/check/typecheck_doc_success/__test__.jsonc create mode 100644 tests/specs/check/typecheck_doc_success/mod.out create mode 100644 tests/specs/check/typecheck_doc_success/mod.ts diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 3b4661054ff45b..e8a1893fb71821 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -113,6 +113,7 @@ pub struct CacheFlags { #[derive(Clone, Debug, Eq, PartialEq)] pub struct CheckFlags { pub files: Vec, + pub doc: bool, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -1750,6 +1751,12 @@ Unless --reload is specified, this command will not re-download already cached d .conflicts_with("no-remote") .hide(true) ) + .arg( + Arg::new("doc") + .long("doc") + .help("Type-check code blocks in JSDoc and Markdown") + .action(ArgAction::SetTrue) + ) .arg( Arg::new("file") .num_args(1..) @@ -4172,7 +4179,10 @@ fn check_parse(flags: &mut Flags, matches: &mut ArgMatches) { if matches.get_flag("all") || matches.get_flag("remote") { flags.type_check_mode = TypeCheckMode::All; } - flags.subcommand = DenoSubcommand::Check(CheckFlags { files }); + flags.subcommand = DenoSubcommand::Check(CheckFlags { + files, + doc: matches.get_flag("doc"), + }); } fn clean_parse(flags: &mut Flags, _matches: &mut ArgMatches) { @@ -6871,6 +6881,20 @@ mod tests { Flags { subcommand: DenoSubcommand::Check(CheckFlags { files: svec!["script.ts"], + doc: false, + }), + type_check_mode: TypeCheckMode::Local, + ..Flags::default() + } + ); + + let r = flags_from_vec(svec!["deno", "check", "--doc", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Check(CheckFlags { + files: svec!["script.ts"], + doc: true, }), type_check_mode: TypeCheckMode::Local, ..Flags::default() @@ -6884,6 +6908,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Check(CheckFlags { files: svec!["script.ts"], + doc: false, }), type_check_mode: TypeCheckMode::All, ..Flags::default() diff --git a/cli/main.rs b/cli/main.rs index 0fdd2cb8b90a50..fbd5c049a798c9 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -132,12 +132,7 @@ async fn run_subcommand(flags: Arc) -> Result { emitter.cache_module_emits(&main_graph_container.graph()).await }), DenoSubcommand::Check(check_flags) => spawn_subcommand(async move { - let factory = CliFactory::from_flags(flags); - let main_graph_container = - factory.main_module_graph_container().await?; - main_graph_container - .load_and_type_check_files(&check_flags.files) - .await + tools::check::check(flags, check_flags).await }), DenoSubcommand::Clean => spawn_subcommand(async move { tools::clean::clean() diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 4ec677f8f31f24..9c109947178caf 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -10,11 +10,14 @@ use deno_core::error::AnyError; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_runtime::deno_node::NodeResolver; +use deno_runtime::deno_permissions::PermissionsContainer; use deno_terminal::colors; use once_cell::sync::Lazy; use regex::Regex; +use crate::args::CheckFlags; use crate::args::CliOptions; +use crate::args::Flags; use crate::args::TsConfig; use crate::args::TsConfigType; use crate::args::TsTypeLib; @@ -23,6 +26,8 @@ use crate::cache::CacheDBHash; use crate::cache::Caches; use crate::cache::FastInsecureHasher; use crate::cache::TypeCheckCache; +use crate::extract; +use crate::factory::CliFactory; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; use crate::npm::CliNpmResolver; @@ -30,6 +35,56 @@ use crate::tsc; use crate::tsc::Diagnostics; use crate::util::path::to_percent_decoded_str; +pub async fn check( + flags: Arc, + check_flags: CheckFlags, +) -> Result<(), AnyError> { + let factory = CliFactory::from_flags(flags); + + let main_graph_container = factory.main_module_graph_container().await?; + + let specifiers = + main_graph_container.collect_specifiers(&check_flags.files)?; + if specifiers.is_empty() { + log::warn!("{} No matching files found.", colors::yellow("Warning")); + } + + let specifiers_for_typecheck = if check_flags.doc { + let file_fetcher = factory.file_fetcher()?; + + let mut specifiers_for_typecheck = specifiers + .iter() + .filter(|specifier| { + use MediaType::*; + matches!( + MediaType::from_specifier(specifier), + TypeScript | JavaScript | Tsx | Jsx | Mts | Mjs | Cts | Cjs + ) + }) + .cloned() + .collect::>(); + + for s in &specifiers { + let file = file_fetcher + .fetch(s, &PermissionsContainer::allow_all()) + .await?; + let snippet_files = extract::extract_snippet_files(file)?; + for snippet_file in snippet_files { + specifiers_for_typecheck.push(snippet_file.specifier.clone()); + file_fetcher.insert_memory_files(snippet_file); + } + } + + specifiers_for_typecheck + } else { + specifiers + }; + + main_graph_container + .check_specifiers(&specifiers_for_typecheck) + .await +} + /// Options for performing a check of a module graph. Note that the decision to /// emit or not is determined by the `ts_config` settings. pub struct CheckOptions { diff --git a/tests/integration/check_tests.rs b/tests/integration/check_tests.rs index f0e3dfb5ad8fe2..bf567be97e6a02 100644 --- a/tests/integration/check_tests.rs +++ b/tests/integration/check_tests.rs @@ -194,7 +194,7 @@ fn reload_flag() { fn typecheck_declarations_ns() { let context = TestContextBuilder::for_jsr().build(); let args = vec![ - "test".to_string(), + "check".to_string(), "--doc".to_string(), util::root_path() .join("cli/tsc/dts/lib.deno.ns.d.ts") @@ -217,9 +217,8 @@ fn typecheck_declarations_ns() { fn typecheck_declarations_unstable() { let context = TestContext::default(); let args = vec![ - "test".to_string(), + "check".to_string(), "--doc".to_string(), - "--unstable".to_string(), util::root_path() .join("cli/tsc/dts/lib.deno.unstable.d.ts") .to_string_lossy() diff --git a/tests/specs/check/typecheck_doc_failure/__test__.jsonc b/tests/specs/check/typecheck_doc_failure/__test__.jsonc new file mode 100644 index 00000000000000..5d95f2666826c1 --- /dev/null +++ b/tests/specs/check/typecheck_doc_failure/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "check --doc mod.ts", + "exitCode": 1, + "output": "mod.out" +} diff --git a/tests/specs/check/typecheck_doc_failure/mod.out b/tests/specs/check/typecheck_doc_failure/mod.out new file mode 100644 index 00000000000000..f32d7049e073ba --- /dev/null +++ b/tests/specs/check/typecheck_doc_failure/mod.out @@ -0,0 +1,6 @@ +Check [WILDCARD]/tests/specs/check/typecheck_doc_failure/mod.ts +Check [WILDCARD]/tests/specs/check/typecheck_doc_failure/mod.ts$2-5.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const sum: string = add(1, 2); + ~~~ + at [WILDCARD]/tests/specs/check/typecheck_doc_failure/mod.ts$2-5.ts:2:7 diff --git a/tests/specs/check/typecheck_doc_failure/mod.ts b/tests/specs/check/typecheck_doc_failure/mod.ts new file mode 100644 index 00000000000000..281d7f41b97ab3 --- /dev/null +++ b/tests/specs/check/typecheck_doc_failure/mod.ts @@ -0,0 +1,8 @@ +/** + * ```ts + * const sum: string = add(1, 2); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} diff --git a/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc b/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc new file mode 100644 index 00000000000000..303e2628a7e4cd --- /dev/null +++ b/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "check --doc markdown.md", + "exitCode": 1, + "output": "markdown.out" +} diff --git a/tests/specs/check/typecheck_doc_in_markdown/markdown.md b/tests/specs/check/typecheck_doc_in_markdown/markdown.md new file mode 100644 index 00000000000000..d18dbd108761a9 --- /dev/null +++ b/tests/specs/check/typecheck_doc_in_markdown/markdown.md @@ -0,0 +1,31 @@ +# Documentation + +The following block does not have a language attribute and should be ignored: + +``` +This is a fenced block without attributes, it's invalid and it should be ignored. +``` + +The following block should be given a js extension on extraction: + +```js +console.log("js"); +``` + +The following block should be given a ts extension on extraction: + +```ts +console.log("ts"); +``` + +The following example contains the ignore attribute and will be ignored: + +```ts ignore +const value: Invalid = "ignored"; +``` + +The following example will trigger the type-checker to fail: + +```ts +const a: string = 42; +``` diff --git a/tests/specs/check/typecheck_doc_in_markdown/markdown.out b/tests/specs/check/typecheck_doc_in_markdown/markdown.out new file mode 100644 index 00000000000000..5c27a87b25cef5 --- /dev/null +++ b/tests/specs/check/typecheck_doc_in_markdown/markdown.out @@ -0,0 +1,7 @@ +Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$11-14.js +Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$17-20.ts +Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. + const a: string = 42; + ^ + at [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts:2:11 diff --git a/tests/specs/check/typecheck_doc_success/__test__.jsonc b/tests/specs/check/typecheck_doc_success/__test__.jsonc new file mode 100644 index 00000000000000..24fee3f2c906a9 --- /dev/null +++ b/tests/specs/check/typecheck_doc_success/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "check --doc mod.ts", + "exitCode": 0, + "output": "mod.out" +} diff --git a/tests/specs/check/typecheck_doc_success/mod.out b/tests/specs/check/typecheck_doc_success/mod.out new file mode 100644 index 00000000000000..8658af4f891ef7 --- /dev/null +++ b/tests/specs/check/typecheck_doc_success/mod.out @@ -0,0 +1,2 @@ +Check [WILDCARD]/tests/specs/check/typecheck_doc_success/mod.ts +Check [WILDCARD]/tests/specs/check/typecheck_doc_success/mod.ts$2-5.ts diff --git a/tests/specs/check/typecheck_doc_success/mod.ts b/tests/specs/check/typecheck_doc_success/mod.ts new file mode 100644 index 00000000000000..793be27117139c --- /dev/null +++ b/tests/specs/check/typecheck_doc_success/mod.ts @@ -0,0 +1,12 @@ +/** + * ```ts + * const sum: number = add(1, 2); + * ``` + * + * ```mts ignore + * const sum: boolean = add(3, 4); + * ``` + */ +export function add(a: number, b: number): number { + return a + b; +} From 56f0c47df851270ca37e00c919ecad0e05ba3520 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 30 Aug 2024 01:34:27 +0900 Subject: [PATCH 16/31] remove unused struct --- cli/extract.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/cli/extract.rs b/cli/extract.rs index d0322feaaef555..3eea8c826c6bce 100644 --- a/cli/extract.rs +++ b/cli/extract.rs @@ -436,34 +436,6 @@ fn extract_sym_from_pat(pat: &ast::Pat) -> Vec { atoms } -#[derive(Default)] -struct ImportedIdentifierCollector { - idents: Vec, -} - -impl Visit for ImportedIdentifierCollector { - fn visit_import_named_specifier( - &mut self, - import_named_specifier: &ast::ImportNamedSpecifier, - ) { - self.idents.push(import_named_specifier.local.clone()); - } - - fn visit_import_default_specifier( - &mut self, - import_default_specifier: &ast::ImportDefaultSpecifier, - ) { - self.idents.push(import_default_specifier.local.clone()); - } - - fn visit_import_star_as_specifier( - &mut self, - import_star_as_specifier: &ast::ImportStarAsSpecifier, - ) { - self.idents.push(import_star_as_specifier.local.clone()); - } -} - /// Generates a "pseudo" file from a given file by applying the following /// transformations: /// From 4769d56c373093ad51b17711fcc5c5f7f03ec00c Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 30 Aug 2024 01:45:28 +0900 Subject: [PATCH 17/31] add test --- cli/extract.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cli/extract.rs b/cli/extract.rs index 3eea8c826c6bce..e27a79347a8021 100644 --- a/cli/extract.rs +++ b/cli/extract.rs @@ -940,6 +940,33 @@ Deno.test("file:///main.ts$3-7.ts", async ()=>{ const foo = createFoo(); foo(); }); +"#, + specifier: "file:///main.ts$3-7.ts", + media_type: MediaType::TypeScript, + }], + }, + // example code has an exported item `foo` - because `export` must be at + // the top level, `foo` is "hoisted" to the top level instead of being + // wrapped in `Deno.test`. + Test { + input: Input { + source: r#" +/** + * ```ts + * doSomething(); + * export const foo = 42; + * ``` + */ +export function doSomething() {} +"#, + specifier: "file:///main.ts", + }, + expected: vec![Expected { + source: r#"export const foo = 42; +import { doSomething } from "file:///main.ts"; +Deno.test("file:///main.ts$3-7.ts", async ()=>{ + doSomething(); +}); "#, specifier: "file:///main.ts$3-7.ts", media_type: MediaType::TypeScript, From de4854faccc3cf49e54a0cf254e1d4f4130498da Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 30 Aug 2024 02:49:38 +0900 Subject: [PATCH 18/31] fix tests --- tests/specs/check/typecheck_doc_in_markdown/markdown.out | 6 +++--- tests/specs/test/type_check_with_doc/main.out | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/specs/check/typecheck_doc_in_markdown/markdown.out b/tests/specs/check/typecheck_doc_in_markdown/markdown.out index 5c27a87b25cef5..b21bc580410f8e 100644 --- a/tests/specs/check/typecheck_doc_in_markdown/markdown.out +++ b/tests/specs/check/typecheck_doc_in_markdown/markdown.out @@ -2,6 +2,6 @@ Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$11-14.js Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$17-20.ts Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. - const a: string = 42; - ^ - at [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts:2:11 +const a: string = 42; + ^ + at [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts:1:7 diff --git a/tests/specs/test/type_check_with_doc/main.out b/tests/specs/test/type_check_with_doc/main.out index 931a6a5f3ef279..56b7ba9e8b9c64 100644 --- a/tests/specs/test/type_check_with_doc/main.out +++ b/tests/specs/test/type_check_with_doc/main.out @@ -6,8 +6,8 @@ const a: string = 1; at file://[WILDCARD]/main.ts:8:7 TS2322 [ERROR]: Type 'string' is not assignable to type 'number'. -const b: number = "1"; - ^ - at file://[WILDCARD]/main.ts$2-5.ts:1:7 + const b: number = "1"; + ^ + at file://[WILDCARD]/main.ts$2-5.ts:2:11 Found 2 errors. From 8eb6868eb15f688dbd01f36dba19021b8f228409 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 30 Aug 2024 10:25:58 +0900 Subject: [PATCH 19/31] fix test for windows --- tests/integration/watcher_tests.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/integration/watcher_tests.rs b/tests/integration/watcher_tests.rs index 0fd62591e89dae..668cac6b4c5855 100644 --- a/tests/integration/watcher_tests.rs +++ b/tests/integration/watcher_tests.rs @@ -1184,6 +1184,9 @@ async fn test_watch_doc() { wait_contains("Test finished", &mut stderr_lines).await; let foo_file = t.path().join("foo.ts"); + // For windows + let foo_file_path_for_display = + foo_file.to_string().replace(std::path::MAIN_SEPARATOR, "/"); foo_file.write( r#" export function add(a: number, b: number) { @@ -1211,7 +1214,7 @@ async fn test_watch_doc() { assert_eq!( skip_restarting_line(&mut stderr_lines).await, - format!("Check file://{foo_file}$3-6.ts") + format!("Check file://{foo_file_path_for_display}$3-6.ts") ); assert_eq!( next_line(&mut stderr_lines).await.unwrap(), @@ -1224,7 +1227,7 @@ async fn test_watch_doc() { assert_eq!(next_line(&mut stderr_lines).await.unwrap(), " ~~~"); assert_eq!( next_line(&mut stderr_lines).await.unwrap(), - format!(" at file://{foo_file}$3-6.ts:3:11") + format!(" at file://{foo_file_path_for_display}$3-6.ts:3:11") ); wait_contains("Test failed", &mut stderr_lines).await; @@ -1247,7 +1250,7 @@ async fn test_watch_doc() { wait_contains("running 1 test from", &mut stdout_lines).await; assert_contains!( next_line(&mut stdout_lines).await.unwrap(), - &format!("{foo_file}$3-8.ts ... FAILED") + &format!("{foo_file_path_for_display}$3-8.ts ... FAILED") ); wait_contains("ERRORS", &mut stdout_lines).await; wait_contains( @@ -1281,7 +1284,7 @@ async fn test_watch_doc() { wait_contains("running 1 test from", &mut stdout_lines).await; assert_contains!( next_line(&mut stdout_lines).await.unwrap(), - &format!("file://{foo_file}$3-8.ts ... ok") + &format!("file://{foo_file_path_for_display}$3-8.ts ... ok") ); wait_contains("ok | 1 passed | 0 failed", &mut stdout_lines).await; From 9dbeed56c9af07cccefe684a512e91376a62f6d8 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 30 Aug 2024 11:16:22 +0900 Subject: [PATCH 20/31] try using url_file() --- tests/integration/watcher_tests.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/integration/watcher_tests.rs b/tests/integration/watcher_tests.rs index 668cac6b4c5855..bef316c0668909 100644 --- a/tests/integration/watcher_tests.rs +++ b/tests/integration/watcher_tests.rs @@ -1184,9 +1184,7 @@ async fn test_watch_doc() { wait_contains("Test finished", &mut stderr_lines).await; let foo_file = t.path().join("foo.ts"); - // For windows - let foo_file_path_for_display = - foo_file.to_string().replace(std::path::MAIN_SEPARATOR, "/"); + let foo_file_url = foo_file.url_file(); foo_file.write( r#" export function add(a: number, b: number) { @@ -1214,7 +1212,7 @@ async fn test_watch_doc() { assert_eq!( skip_restarting_line(&mut stderr_lines).await, - format!("Check file://{foo_file_path_for_display}$3-6.ts") + format!("Check {foo_file_url}$3-6.ts") ); assert_eq!( next_line(&mut stderr_lines).await.unwrap(), @@ -1227,7 +1225,7 @@ async fn test_watch_doc() { assert_eq!(next_line(&mut stderr_lines).await.unwrap(), " ~~~"); assert_eq!( next_line(&mut stderr_lines).await.unwrap(), - format!(" at file://{foo_file_path_for_display}$3-6.ts:3:11") + format!(" at {foo_file_url}$3-6.ts:3:11") ); wait_contains("Test failed", &mut stderr_lines).await; @@ -1250,7 +1248,7 @@ async fn test_watch_doc() { wait_contains("running 1 test from", &mut stdout_lines).await; assert_contains!( next_line(&mut stdout_lines).await.unwrap(), - &format!("{foo_file_path_for_display}$3-8.ts ... FAILED") + &format!("{foo_file_url}$3-8.ts ... FAILED") ); wait_contains("ERRORS", &mut stdout_lines).await; wait_contains( @@ -1284,7 +1282,7 @@ async fn test_watch_doc() { wait_contains("running 1 test from", &mut stdout_lines).await; assert_contains!( next_line(&mut stdout_lines).await.unwrap(), - &format!("file://{foo_file_path_for_display}$3-8.ts ... ok") + &format!("{foo_file_url}$3-8.ts ... ok") ); wait_contains("ok | 1 passed | 0 failed", &mut stdout_lines).await; From e2b8135c2a8098d62d724f847228d93fadaf901e Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 30 Aug 2024 12:28:28 +0900 Subject: [PATCH 21/31] add a test --- .../__test__.jsonc | 5 +++++ .../typecheck_doc_duplicate_identifiers/mod.out | 2 ++ .../typecheck_doc_duplicate_identifiers/mod.ts | 13 +++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc create mode 100644 tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out create mode 100644 tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts diff --git a/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc b/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc new file mode 100644 index 00000000000000..8596142ddec87e --- /dev/null +++ b/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "check --doc --config ../../../config/deno.json mod.ts", + "exitCode": 0, + "output": "mod.out" +} diff --git a/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out b/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out new file mode 100644 index 00000000000000..e4ac0c64778882 --- /dev/null +++ b/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out @@ -0,0 +1,2 @@ +Check [WILDCARD]/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts +Check [WILDCARD]/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts$2-8.ts diff --git a/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts b/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts new file mode 100644 index 00000000000000..576f702400b775 --- /dev/null +++ b/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts @@ -0,0 +1,13 @@ +/** + * ```ts + * import { assertEquals } from "@std/assert/equals"; + * + * const foo = createFoo(3); + * assertEquals(foo, 9); + * ``` + */ +export function createFoo(x: number): number { + return x * x; +} + +export const foo = 42; From 53b6e85c243bfd66f7acac6605d70f217e131de5 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Thu, 12 Sep 2024 20:33:52 +0900 Subject: [PATCH 22/31] make expected test outputs not rely on the specific directory structure --- .../mod.out | 4 +- .../specs/check/typecheck_doc_failure/mod.out | 6 +-- .../typecheck_doc_in_markdown/markdown.out | 8 ++-- .../test/doc_duplicate_identifier/main.out | 10 ++--- tests/specs/test/doc_failure/main.out | 38 +++++++++---------- tests/specs/test/doc_only/doc_only.out | 4 +- tests/specs/test/doc_success/main.out | 20 +++++----- .../test/doc_ts_declare_global/lib.d.ts.out | 4 +- .../test/doc_ts_namespace_decl/lib.d.ts.out | 4 +- tests/specs/test/markdown/markdown.out | 8 ++-- .../markdown_full_block_names.out | 6 +-- .../markdown_with_comment.out | 4 +- .../markdown_windows/markdown_windows.out | 8 ++-- 13 files changed, 62 insertions(+), 62 deletions(-) diff --git a/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out b/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out index e4ac0c64778882..d01daafa5aadae 100644 --- a/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out +++ b/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.out @@ -1,2 +1,2 @@ -Check [WILDCARD]/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts -Check [WILDCARD]/tests/specs/check/typecheck_doc_duplicate_identifiers/mod.ts$2-8.ts +Check [WILDCARD]/mod.ts +Check [WILDCARD]/mod.ts$2-8.ts diff --git a/tests/specs/check/typecheck_doc_failure/mod.out b/tests/specs/check/typecheck_doc_failure/mod.out index f32d7049e073ba..61fd5499e50a6b 100644 --- a/tests/specs/check/typecheck_doc_failure/mod.out +++ b/tests/specs/check/typecheck_doc_failure/mod.out @@ -1,6 +1,6 @@ -Check [WILDCARD]/tests/specs/check/typecheck_doc_failure/mod.ts -Check [WILDCARD]/tests/specs/check/typecheck_doc_failure/mod.ts$2-5.ts +Check [WILDCARD]/mod.ts +Check [WILDCARD]/mod.ts$2-5.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. const sum: string = add(1, 2); ~~~ - at [WILDCARD]/tests/specs/check/typecheck_doc_failure/mod.ts$2-5.ts:2:7 + at [WILDCARD]/mod.ts$2-5.ts:2:7 diff --git a/tests/specs/check/typecheck_doc_in_markdown/markdown.out b/tests/specs/check/typecheck_doc_in_markdown/markdown.out index b21bc580410f8e..acc05dc818baac 100644 --- a/tests/specs/check/typecheck_doc_in_markdown/markdown.out +++ b/tests/specs/check/typecheck_doc_in_markdown/markdown.out @@ -1,7 +1,7 @@ -Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$11-14.js -Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$17-20.ts -Check [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts +Check [WILDCARD]/markdown.md$11-14.js +Check [WILDCARD]/markdown.md$17-20.ts +Check [WILDCARD]/markdown.md$29-32.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. const a: string = 42; ^ - at [WILDCARD]/specs/check/typecheck_doc_in_markdown/markdown.md$29-32.ts:1:7 + at [WILDCARD]/markdown.md$29-32.ts:1:7 diff --git a/tests/specs/test/doc_duplicate_identifier/main.out b/tests/specs/test/doc_duplicate_identifier/main.out index ffcec773ae1bec..9196405a653533 100644 --- a/tests/specs/test/doc_duplicate_identifier/main.out +++ b/tests/specs/test/doc_duplicate_identifier/main.out @@ -1,11 +1,11 @@ -Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts -Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$11-19.ts -Check [WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$25-30.ts +Check [WILDCARD]/main.ts +Check [WILDCARD]/main.ts$11-19.ts +Check [WILDCARD]/main.ts$25-30.ts running 0 tests from ./main.ts running 1 test from ./main.ts$11-19.ts -[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$11-19.ts ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$11-19.ts ... ok ([WILDCARD]ms) running 1 test from ./main.ts$25-30.ts -[WILDCARD]/tests/specs/test/doc_duplicate_identifier/main.ts$25-30.ts ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$25-30.ts ... ok ([WILDCARD]ms) ok | 2 passed | 0 failed ([WILDCARD]ms) diff --git a/tests/specs/test/doc_failure/main.out b/tests/specs/test/doc_failure/main.out index b7aa0d0f203772..963d7da43d0788 100644 --- a/tests/specs/test/doc_failure/main.out +++ b/tests/specs/test/doc_failure/main.out @@ -1,18 +1,18 @@ -Check [WILDCARD]/tests/specs/test/doc_failure/main.ts -Check [WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts -Check [WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts -Check [WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts +Check [WILDCARD]/main.ts +Check [WILDCARD]/main.ts$2-9.ts +Check [WILDCARD]/main.ts$13-18.ts +Check [WILDCARD]/main.ts$24-29.ts running 0 tests from ./main.ts running 1 test from ./main.ts$2-9.ts -[WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts ... FAILED ([WILDCARD]ms) +[WILDCARD]/main.ts$2-9.ts ... FAILED ([WILDCARD]ms) running 1 test from ./main.ts$13-18.ts -[WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts ... FAILED ([WILDCARD]ms) +[WILDCARD]/main.ts$13-18.ts ... FAILED ([WILDCARD]ms) running 1 test from ./main.ts$24-29.ts -[WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts ... FAILED ([WILDCARD]ms) +[WILDCARD]/main.ts$24-29.ts ... FAILED ([WILDCARD]ms) ERRORS -[WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts => ./main.ts$13-18.ts:3:6 +[WILDCARD]/main.ts$13-18.ts => ./main.ts$13-18.ts:3:6 error: AssertionError: Values are not equal. @@ -24,17 +24,17 @@ error: AssertionError: Values are not equal. throw new AssertionError(message); ^ - at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) - at [WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts:4:5 + at assertEquals ([WILDCARD]/std/assert/equals.ts:47:9) + at [WILDCARD]/main.ts$13-18.ts:4:5 -[WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 +[WILDCARD]/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 error: AssertionError: Expected actual: "2.5e+0" to be close to "2": delta "5e-1" is greater than "2e-7". throw new AssertionError( ^ - at assertAlmostEquals ([WILDCARD]/tests/util/std/assert/almost_equals.ts:51:9) - at [WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts:6:5 + at assertAlmostEquals ([WILDCARD]/std/assert/almost_equals.ts:51:9) + at [WILDCARD]/main.ts$2-9.ts:6:5 -[WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 +[WILDCARD]/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 error: AssertionError: Values are not equal. @@ -46,14 +46,14 @@ error: AssertionError: Values are not equal. throw new AssertionError(message); ^ - at assertEquals ([WILDCARD]/tests/util/std/assert/equals.ts:47:9) - at [WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts:4:5 + at assertEquals ([WILDCARD]/std/assert/equals.ts:47:9) + at [WILDCARD]/main.ts$24-29.ts:4:5 FAILURES -[WILDCARD]/tests/specs/test/doc_failure/main.ts$13-18.ts => ./main.ts$13-18.ts:3:6 -[WILDCARD]/tests/specs/test/doc_failure/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 -[WILDCARD]/tests/specs/test/doc_failure/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 +[WILDCARD]/main.ts$13-18.ts => ./main.ts$13-18.ts:3:6 +[WILDCARD]/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 +[WILDCARD]/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 FAILED | 0 passed | 3 failed ([WILDCARD]ms) diff --git a/tests/specs/test/doc_only/doc_only.out b/tests/specs/test/doc_only/doc_only.out index b73301e200da3e..5313e01aec4688 100644 --- a/tests/specs/test/doc_only/doc_only.out +++ b/tests/specs/test/doc_only/doc_only.out @@ -1,6 +1,6 @@ -Check [WILDCARD]/specs/test/doc_only/dir/mod.ts$2-7.ts +Check [WILDCARD]/dir/mod.ts$2-7.ts running 1 test from ./dir/mod.ts$2-7.ts -[WILDCARD]/specs/test/doc_only/dir/mod.ts$2-7.ts ... ok ([WILDCARD]ms) +[WILDCARD]/dir/mod.ts$2-7.ts ... ok ([WILDCARD]ms) ok | 1 passed | 0 failed ([WILDCARD]ms) diff --git a/tests/specs/test/doc_success/main.out b/tests/specs/test/doc_success/main.out index 77aa702ddfd0fa..cf1e69b6711811 100644 --- a/tests/specs/test/doc_success/main.out +++ b/tests/specs/test/doc_success/main.out @@ -1,19 +1,19 @@ -Check [WILDCARD]/specs/test/doc_success/main.ts$8-13.js -Check [WILDCARD]/specs/test/doc_success/main.ts$14-19.jsx -Check [WILDCARD]/specs/test/doc_success/main.ts$20-25.ts -Check [WILDCARD]/specs/test/doc_success/main.ts$26-31.tsx -Check [WILDCARD]/specs/test/doc_success/main.ts$42-47.ts +Check [WILDCARD]/main.ts$8-13.js +Check [WILDCARD]/main.ts$14-19.jsx +Check [WILDCARD]/main.ts$20-25.ts +Check [WILDCARD]/main.ts$26-31.tsx +Check [WILDCARD]/main.ts$42-47.ts running 0 tests from ./main.ts running 1 test from ./main.ts$8-13.js -[WILDCARD]/specs/test/doc_success/main.ts$8-13.js ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$8-13.js ... ok ([WILDCARD]ms) running 1 test from ./main.ts$14-19.jsx -[WILDCARD]/specs/test/doc_success/main.ts$14-19.jsx ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$14-19.jsx ... ok ([WILDCARD]ms) running 1 test from ./main.ts$20-25.ts -[WILDCARD]/specs/test/doc_success/main.ts$20-25.ts ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$20-25.ts ... ok ([WILDCARD]ms) running 1 test from ./main.ts$26-31.tsx -[WILDCARD]/specs/test/doc_success/main.ts$26-31.tsx ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$26-31.tsx ... ok ([WILDCARD]ms) running 1 test from ./main.ts$42-47.ts -[WILDCARD]/specs/test/doc_success/main.ts$42-47.ts ... ok ([WILDCARD]ms) +[WILDCARD]/main.ts$42-47.ts ... ok ([WILDCARD]ms) ok | 5 passed | 0 failed ([WILDCARD]ms) diff --git a/tests/specs/test/doc_ts_declare_global/lib.d.ts.out b/tests/specs/test/doc_ts_declare_global/lib.d.ts.out index 88021f61adc071..2d6d8dbc853ae9 100644 --- a/tests/specs/test/doc_ts_declare_global/lib.d.ts.out +++ b/tests/specs/test/doc_ts_declare_global/lib.d.ts.out @@ -1,6 +1,6 @@ -Check [WILDCARD]/tests/specs/test/doc_ts_declare_global/lib$d$ts$5-11.ts +Check [WILDCARD]/lib$d$ts$5-11.ts running 1 test from ./lib$d$ts$5-11.ts -[WILDCARD]/tests/specs/test/doc_ts_declare_global/lib$d$ts$5-11.ts ... ok ([WILDCARD]ms) +[WILDCARD]/lib$d$ts$5-11.ts ... ok ([WILDCARD]ms) ok | 1 passed | 0 failed ([WILDCARD]ms) diff --git a/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out b/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out index c235da376d5cf7..2c9e71dc4ac0aa 100644 --- a/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out +++ b/tests/specs/test/doc_ts_namespace_decl/lib.d.ts.out @@ -1,6 +1,6 @@ -Check [WILDCARD]/tests/specs/test/doc_ts_namespace_decl/lib$d$ts$3-9.ts +Check [WILDCARD]/lib$d$ts$3-9.ts running 1 test from ./lib$d$ts$3-9.ts -[WILDCARD]/tests/specs/test/doc_ts_namespace_decl/lib$d$ts$3-9.ts ... ok ([WILDCARD]ms) +[WILDCARD]/lib$d$ts$3-9.ts ... ok ([WILDCARD]ms) ok | 1 passed | 0 failed ([WILDCARD]ms) diff --git a/tests/specs/test/markdown/markdown.out b/tests/specs/test/markdown/markdown.out index e90f05be40723b..416f79e22af264 100644 --- a/tests/specs/test/markdown/markdown.out +++ b/tests/specs/test/markdown/markdown.out @@ -1,7 +1,7 @@ -Check [WILDCARD]/specs/test/markdown/markdown.md$11-14.js -Check [WILDCARD]/specs/test/markdown/markdown.md$17-20.ts -Check [WILDCARD]/specs/test/markdown/markdown.md$29-32.ts +Check [WILDCARD]/markdown.md$11-14.js +Check [WILDCARD]/markdown.md$17-20.ts +Check [WILDCARD]/markdown.md$29-32.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. const a: string = 42; ^ - at [WILDCARD]/specs/test/markdown/markdown.md$29-32.ts:2:11 + at [WILDCARD]/markdown.md$29-32.ts:2:11 diff --git a/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out b/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out index a4630a5696479f..e838a4441ac3b6 100644 --- a/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out +++ b/tests/specs/test/markdown_full_block_names/markdown_full_block_names.out @@ -1,6 +1,6 @@ -Check [WILDCARD]/specs/test/markdown_full_block_names/markdown_full_block_names.md$5-8.js -Check [WILDCARD]/specs/test/markdown_full_block_names/markdown_full_block_names.md$17-20.ts +Check [WILDCARD]/markdown_full_block_names.md$5-8.js +Check [WILDCARD]/markdown_full_block_names.md$17-20.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. const a: string = 42; ^ - at [WILDCARD]/specs/test/markdown_full_block_names/markdown_full_block_names.md$17-20.ts:2:11 + at [WILDCARD]/markdown_full_block_names.md$17-20.ts:2:11 diff --git a/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out b/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out index 00511dcee37444..dcd97ae4dd9970 100644 --- a/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out +++ b/tests/specs/test/markdown_ignore_html_comment/markdown_with_comment.out @@ -1,5 +1,5 @@ -Check [WILDCARD]/specs/test/markdown_ignore_html_comment/markdown_with_comment.md$34-37.ts +Check [WILDCARD]/markdown_with_comment.md$34-37.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. const a: string = 42; ^ - at [WILDCARD]/test/markdown_ignore_html_comment/markdown_with_comment.md$34-37.ts:2:11 + at [WILDCARD]/markdown_with_comment.md$34-37.ts:2:11 diff --git a/tests/specs/test/markdown_windows/markdown_windows.out b/tests/specs/test/markdown_windows/markdown_windows.out index 0de7fce70c395e..15199d87b22a36 100644 --- a/tests/specs/test/markdown_windows/markdown_windows.out +++ b/tests/specs/test/markdown_windows/markdown_windows.out @@ -1,7 +1,7 @@ -Check [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$11-14.js -Check [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$17-20.ts -Check [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$29-32.ts +Check [WILDCARD]/markdown_windows.md$11-14.js +Check [WILDCARD]/markdown_windows.md$17-20.ts +Check [WILDCARD]/markdown_windows.md$29-32.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. const a: string = 42; ^ - at [WILDCARD]/specs/test/markdown_windows/markdown_windows.md$29-32.ts:2:11 + at [WILDCARD]/markdown_windows.md$29-32.ts:2:11 From cef7c948d4407ce6e81ff765c7f345f4bf0dbb3e Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Thu, 12 Sep 2024 20:38:51 +0900 Subject: [PATCH 23/31] move extract.rs to one level deeper --- cli/tools/check.rs | 2 +- cli/tools/test/mod.rs | 2 +- cli/{ => util}/extract.rs | 0 cli/util/mod.rs | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) rename cli/{ => util}/extract.rs (100%) diff --git a/cli/tools/check.rs b/cli/tools/check.rs index bb061effb80b13..620eed34916f72 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -27,13 +27,13 @@ use crate::cache::CacheDBHash; use crate::cache::Caches; use crate::cache::FastInsecureHasher; use crate::cache::TypeCheckCache; -use crate::extract; use crate::factory::CliFactory; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; use crate::npm::CliNpmResolver; use crate::tsc; use crate::tsc::Diagnostics; +use crate::util::extract; use crate::util::path::to_percent_decoded_str; pub async fn check( diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index b7a3bd0edb6861..e582a5d25124fa 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -6,11 +6,11 @@ use crate::args::TestFlags; use crate::args::TestReporterConfig; use crate::colors; use crate::display; -use crate::extract::extract_doc_tests; use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::graph_util::has_graph_root_local_dependent_changed; use crate::ops; +use crate::util::extract::extract_doc_tests; use crate::util::file_watcher; use crate::util::fs::collect_specifiers; use crate::util::path::get_extension; diff --git a/cli/extract.rs b/cli/util/extract.rs similarity index 100% rename from cli/extract.rs rename to cli/util/extract.rs diff --git a/cli/util/mod.rs b/cli/util/mod.rs index b9071c496357ce..e59b09d2c7167e 100644 --- a/cli/util/mod.rs +++ b/cli/util/mod.rs @@ -7,6 +7,7 @@ pub mod console; pub mod diff; pub mod display; pub mod draw_thread; +pub mod extract; pub mod file_watcher; pub mod fs; pub mod logger; From 627972139679d0fa498dd819fb5df5d790dda4c2 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Thu, 12 Sep 2024 23:38:27 +0900 Subject: [PATCH 24/31] remove leftover --- cli/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/main.rs b/cli/main.rs index b98d5634a85c33..2013fb7f5348e3 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -6,7 +6,6 @@ mod cache; mod cdp; mod emit; mod errors; -mod extract; mod factory; mod file_fetcher; mod graph_container; From 00280eb6a27eaca43eea26c106c151ad431432ba Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Thu, 12 Sep 2024 23:39:08 +0900 Subject: [PATCH 25/31] refactor: extract common part of getting pseudo File instances for doc tests --- cli/tools/test/mod.rs | 105 +++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 57 deletions(-) diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index e582a5d25124fa..1e1b6ff7486122 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -7,6 +7,7 @@ use crate::args::TestReporterConfig; use crate::colors; use crate::display; use crate::factory::CliFactory; +use crate::file_fetcher::File; use crate::file_fetcher::FileFetcher; use crate::graph_util::has_graph_root_local_dependent_changed; use crate::ops; @@ -1565,34 +1566,12 @@ pub async fn run_tests( return Err(generic_error("No test modules found")); } - let specifiers_for_typecheck_and_test = { - // Specifiers to extract doc tests from - let specifiers_needing_extraction = specifiers_with_mode - .iter() - .filter(|(_, mode)| mode.needs_test_extraction()) - .map(|(s, _)| s.clone()) - .collect::>(); - - let mut final_specifiers = specifiers_with_mode - .into_iter() - .filter_map(|(s, mode)| mode.needs_test_run().then_some(s)) - .collect::>(); - - // Extract doc tests, add them to the file fetcher as in-memory files, - // and add the specifiers to the final list of specifiers to run tests on. - for s in &specifiers_needing_extraction { - let file = file_fetcher - .fetch(s, &PermissionsContainer::allow_all()) - .await?; - let doc_tests = extract_doc_tests(file)?; - for doc_test in doc_tests { - final_specifiers.push(doc_test.specifier.clone()); - file_fetcher.insert_memory_files(doc_test); - } - } - - final_specifiers - }; + let doc_tests = get_doc_tests(&specifiers_with_mode, file_fetcher).await?; + let specifiers_for_typecheck_and_test = + get_target_specifiers(specifiers_with_mode, &doc_tests); + for doc_test in doc_tests { + file_fetcher.insert_memory_files(doc_test); + } let main_graph_container = factory.main_module_graph_container().await?; @@ -1755,35 +1734,13 @@ pub async fn run_tests_with_watch( .filter(|(specifier, _)| test_modules_to_reload.contains(specifier)) .collect::>(); - let specifiers_for_typecheck_and_test = { - // Specifiers to extract doc tests from - let specifiers_needing_extraction = specifiers_with_mode - .iter() - .filter(|(_, mode)| mode.needs_test_extraction()) - .map(|(s, _)| s.clone()) - .collect::>(); - - let mut final_specifiers = specifiers_with_mode - .into_iter() - .filter_map(|(s, mode)| mode.needs_test_run().then_some(s)) - .collect::>(); - - // Extract doc tests, add them to the file fetcher as in-memory files, - // and add the specifiers to the final list of specifiers to run tests - // on. - for s in &specifiers_needing_extraction { - let file = file_fetcher - .fetch(s, &PermissionsContainer::allow_all()) - .await?; - let doc_tests = extract_doc_tests(file)?; - for doc_test in doc_tests { - final_specifiers.push(doc_test.specifier.clone()); - file_fetcher.insert_memory_files(doc_test); - } - } - - final_specifiers - }; + let doc_tests = + get_doc_tests(&specifiers_with_mode, file_fetcher).await?; + let specifiers_for_typecheck_and_test = + get_target_specifiers(specifiers_with_mode, &doc_tests); + for doc_test in doc_tests { + file_fetcher.insert_memory_files(doc_test); + } let main_graph_container = factory.main_module_graph_container().await?; @@ -1838,6 +1795,40 @@ pub async fn run_tests_with_watch( Ok(()) } +/// Extracts doc tests from files specified by the given specifiers. +async fn get_doc_tests( + specifiers_with_mode: &[(Url, TestMode)], + file_fetcher: &FileFetcher, +) -> Result, AnyError> { + let specifiers_needing_extraction = specifiers_with_mode + .iter() + .filter(|(_, mode)| mode.needs_test_extraction()) + .map(|(s, _)| s); + + let mut doc_tests = Vec::new(); + for s in specifiers_needing_extraction { + let file = file_fetcher + .fetch(s, &PermissionsContainer::allow_all()) + .await?; + doc_tests.extend(extract_doc_tests(file)?); + } + + Ok(doc_tests) +} + +/// Get a list of specifiers that we need to perform typecheck and run tests on. +/// The result includes "pseudo specifiers" for doc tests. +fn get_target_specifiers( + specifiers_with_mode: Vec<(Url, TestMode)>, + doc_tests: &[File], +) -> Vec { + specifiers_with_mode + .into_iter() + .filter_map(|(s, mode)| mode.needs_test_run().then_some(s)) + .chain(doc_tests.iter().map(|d| d.specifier.clone())) + .collect() +} + /// Tracks failures for the `--fail-fast` argument in /// order to tell when to stop running tests. #[derive(Clone, Default)] From 0c8b4c6219c8b3918172bd60068b4ab7e7b79020 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 13 Sep 2024 00:13:27 +0900 Subject: [PATCH 26/31] do not assume specific location where assertion fails --- tests/specs/test/doc_failure/main.out | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/specs/test/doc_failure/main.out b/tests/specs/test/doc_failure/main.out index 963d7da43d0788..01b03297f8629b 100644 --- a/tests/specs/test/doc_failure/main.out +++ b/tests/specs/test/doc_failure/main.out @@ -24,14 +24,14 @@ error: AssertionError: Values are not equal. throw new AssertionError(message); ^ - at assertEquals ([WILDCARD]/std/assert/equals.ts:47:9) + at assertEquals ([WILDCARD]/std/assert/equals.ts:[WILDCARD]) at [WILDCARD]/main.ts$13-18.ts:4:5 [WILDCARD]/main.ts$2-9.ts => ./main.ts$2-9.ts:3:6 error: AssertionError: Expected actual: "2.5e+0" to be close to "2": delta "5e-1" is greater than "2e-7". throw new AssertionError( ^ - at assertAlmostEquals ([WILDCARD]/std/assert/almost_equals.ts:51:9) + at assertAlmostEquals ([WILDCARD]/std/assert/almost_equals.ts:[WILDCARD]) at [WILDCARD]/main.ts$2-9.ts:6:5 [WILDCARD]/main.ts$24-29.ts => ./main.ts$24-29.ts:3:6 @@ -46,7 +46,7 @@ error: AssertionError: Values are not equal. throw new AssertionError(message); ^ - at assertEquals ([WILDCARD]/std/assert/equals.ts:47:9) + at assertEquals ([WILDCARD]/std/assert/equals.ts:[WILDCARD]) at [WILDCARD]/main.ts$24-29.ts:4:5 FAILURES From 13235e26c64ef4b49542dd725bfa100f11c6b9f3 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 13 Sep 2024 12:00:12 +0900 Subject: [PATCH 27/31] add test case to ensure permission works in doc tests --- .../doc_permission_respected/__test__.jsonc | 5 ++++ .../test/doc_permission_respected/main.out | 25 +++++++++++++++++++ .../test/doc_permission_respected/main.ts | 12 +++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/specs/test/doc_permission_respected/__test__.jsonc create mode 100644 tests/specs/test/doc_permission_respected/main.out create mode 100644 tests/specs/test/doc_permission_respected/main.ts diff --git a/tests/specs/test/doc_permission_respected/__test__.jsonc b/tests/specs/test/doc_permission_respected/__test__.jsonc new file mode 100644 index 00000000000000..43c2910844d7ba --- /dev/null +++ b/tests/specs/test/doc_permission_respected/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "test --doc --allow-env=PATH --reload main.ts", + "exitCode": 1, + "output": "main.out" +} diff --git a/tests/specs/test/doc_permission_respected/main.out b/tests/specs/test/doc_permission_respected/main.out new file mode 100644 index 00000000000000..928d4f3cece0de --- /dev/null +++ b/tests/specs/test/doc_permission_respected/main.out @@ -0,0 +1,25 @@ +Check [WILDCARD]/main.ts +Check [WILDCARD]/main.ts$3-6.ts +Check [WILDCARD]/main.ts$8-11.ts +running 0 tests from ./main.ts +running 1 test from ./main.ts$3-6.ts +[WILDCARD]/main.ts$3-6.ts ... ok ([WILDCARD]ms) +running 1 test from ./main.ts$8-11.ts +[WILDCARD]/main.ts$8-11.ts ... FAILED ([WILDCARD]ms) + + ERRORS + +[WILDCARD]/main.ts$8-11.ts => ./main.ts$8-11.ts:1:6 +error: NotCapable: Requires env access to "USER", run again with the --allow-env flag + const _user = Deno.env.get("USER"); + ^ + at Object.getEnv [as get] ([WILDCARD]) + at [WILDCARD]/main.ts$8-11.ts:2:28 + + FAILURES + +[WILDCARD]/main.ts$8-11.ts => ./main.ts$8-11.ts:1:6 + +FAILED | 1 passed | 1 failed ([WILDCARD]ms) + +error: Test failed diff --git a/tests/specs/test/doc_permission_respected/main.ts b/tests/specs/test/doc_permission_respected/main.ts new file mode 100644 index 00000000000000..fdc7743a86a44a --- /dev/null +++ b/tests/specs/test/doc_permission_respected/main.ts @@ -0,0 +1,12 @@ +/** + * This should succeed because we pass `--allow-env=PATH` + * ```ts + * const _path = Deno.env.get("PATH"); + * ``` + * + * This should fail because we don't allow for env access to `USER` + * ```ts + * const _user = Deno.env.get("USER"); + * ``` + * @module doc + */ From fbc7b8ed4d0ffe39b6c8b2e96af47aa8ebefef21 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Fri, 13 Sep 2024 22:00:12 +0900 Subject: [PATCH 28/31] not filter specifiers --- cli/tools/check.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 620eed34916f72..e016318e7bdaa4 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -53,17 +53,7 @@ pub async fn check( let specifiers_for_typecheck = if check_flags.doc { let file_fetcher = factory.file_fetcher()?; - let mut specifiers_for_typecheck = specifiers - .iter() - .filter(|specifier| { - use MediaType::*; - matches!( - MediaType::from_specifier(specifier), - TypeScript | JavaScript | Tsx | Jsx | Mts | Mjs | Cts | Cjs - ) - }) - .cloned() - .collect::>(); + let mut specifiers_for_typecheck = specifiers.clone(); for s in &specifiers { let file = file_fetcher From ff87ee047a4a555d6351092a0a5b886006dbc36f Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 17 Sep 2024 17:04:45 +0900 Subject: [PATCH 29/31] add --doc-only flag to check subcommand --- cli/args/flags.rs | 41 ++++++++++++++++++- cli/tools/check.rs | 12 ++++-- tests/integration/check_tests.rs | 4 +- .../typecheck_doc_in_markdown/__test__.jsonc | 2 +- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 605f286a9ec96e..92336a0a16fdf9 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -110,6 +110,7 @@ pub struct CacheFlags { pub struct CheckFlags { pub files: Vec, pub doc: bool, + pub doc_only: bool, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -1698,9 +1699,16 @@ Unless --reload is specified, this command will not re-download already cached d .arg( Arg::new("doc") .long("doc") - .help("Type-check code blocks in JSDoc and Markdown") + .help("Type-check code blocks in JSDoc as well as actual code") .action(ArgAction::SetTrue) ) + .arg( + Arg::new("doc-only") + .long("doc-only") + .help("Type-check code blocks in JSDoc and Markdown only") + .action(ArgAction::SetTrue) + .conflicts_with("doc") + ) .arg( Arg::new("file") .num_args(1..) @@ -4131,6 +4139,7 @@ fn check_parse( flags.subcommand = DenoSubcommand::Check(CheckFlags { files, doc: matches.get_flag("doc"), + doc_only: matches.get_flag("doc-only"), }); Ok(()) } @@ -6873,6 +6882,7 @@ mod tests { subcommand: DenoSubcommand::Check(CheckFlags { files: svec!["script.ts"], doc: false, + doc_only: false, }), type_check_mode: TypeCheckMode::Local, ..Flags::default() @@ -6886,12 +6896,40 @@ mod tests { subcommand: DenoSubcommand::Check(CheckFlags { files: svec!["script.ts"], doc: true, + doc_only: false, + }), + type_check_mode: TypeCheckMode::Local, + ..Flags::default() + } + ); + + let r = flags_from_vec(svec!["deno", "check", "--doc-only", "markdown.md"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Check(CheckFlags { + files: svec!["markdown.md"], + doc: false, + doc_only: true, }), type_check_mode: TypeCheckMode::Local, ..Flags::default() } ); + // `--doc` and `--doc-only` are mutually exclusive + let r = flags_from_vec(svec![ + "deno", + "check", + "--doc", + "--doc-only", + "script.ts" + ]); + assert_eq!( + r.unwrap_err().kind(), + clap::error::ErrorKind::ArgumentConflict + ); + for all_flag in ["--remote", "--all"] { let r = flags_from_vec(svec!["deno", "check", all_flag, "script.ts"]); assert_eq!( @@ -6900,6 +6938,7 @@ mod tests { subcommand: DenoSubcommand::Check(CheckFlags { files: svec!["script.ts"], doc: false, + doc_only: false, }), type_check_mode: TypeCheckMode::All, ..Flags::default() diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 53823a889a3c0d..9c464fa1694fe8 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -49,13 +49,17 @@ pub async fn check( log::warn!("{} No matching files found.", colors::yellow("Warning")); } - let specifiers_for_typecheck = if check_flags.doc { + let specifiers_for_typecheck = if check_flags.doc || check_flags.doc_only { let file_fetcher = factory.file_fetcher()?; - let mut specifiers_for_typecheck = specifiers.clone(); + let mut specifiers_for_typecheck = if check_flags.doc { + specifiers.clone() + } else { + vec![] + }; - for s in &specifiers { - let file = file_fetcher.fetch_bypass_permissions(s).await?; + for s in specifiers { + let file = file_fetcher.fetch_bypass_permissions(&s).await?; let snippet_files = extract::extract_snippet_files(file)?; for snippet_file in snippet_files { specifiers_for_typecheck.push(snippet_file.specifier.clone()); diff --git a/tests/integration/check_tests.rs b/tests/integration/check_tests.rs index 2e219d290c7a54..121dcb837c80df 100644 --- a/tests/integration/check_tests.rs +++ b/tests/integration/check_tests.rs @@ -186,7 +186,7 @@ fn typecheck_declarations_ns() { let context = TestContextBuilder::for_jsr().build(); let args = vec![ "check".to_string(), - "--doc".to_string(), + "--doc-only".to_string(), util::root_path() .join("cli/tsc/dts/lib.deno.ns.d.ts") .to_string_lossy() @@ -209,7 +209,7 @@ fn typecheck_declarations_unstable() { let context = TestContext::default(); let args = vec![ "check".to_string(), - "--doc".to_string(), + "--doc-only".to_string(), util::root_path() .join("cli/tsc/dts/lib.deno.unstable.d.ts") .to_string_lossy() diff --git a/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc b/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc index 303e2628a7e4cd..00f98c4d0e9224 100644 --- a/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc +++ b/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc @@ -1,5 +1,5 @@ { - "args": "check --doc markdown.md", + "args": "check --doc-only markdown.md", "exitCode": 1, "output": "markdown.out" } From c9f139e08bc929522e7676e6ff869f671747770b Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 17 Sep 2024 17:09:16 +0900 Subject: [PATCH 30/31] fix --- tests/specs/test/doc_only/__test__.jsonc | 4 ++-- tests/specs/test/doc_only/doc_only.out | 6 ------ tests/specs/test/doc_only/{dir => doc_only}/mod.ts | 0 tests/specs/test/doc_only/main.out | 6 ++++-- 4 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 tests/specs/test/doc_only/doc_only.out rename tests/specs/test/doc_only/{dir => doc_only}/mod.ts (100%) diff --git a/tests/specs/test/doc_only/__test__.jsonc b/tests/specs/test/doc_only/__test__.jsonc index b826770b6d75f8..f40260ae7251a9 100644 --- a/tests/specs/test/doc_only/__test__.jsonc +++ b/tests/specs/test/doc_only/__test__.jsonc @@ -1,5 +1,5 @@ { - "args": "test --doc --config ../../../config/deno.json dir", + "args": "test --doc --config ../../../config/deno.json doc_only", "exitCode": 0, - "output": "doc_only.out" + "output": "main.out" } diff --git a/tests/specs/test/doc_only/doc_only.out b/tests/specs/test/doc_only/doc_only.out deleted file mode 100644 index 5313e01aec4688..00000000000000 --- a/tests/specs/test/doc_only/doc_only.out +++ /dev/null @@ -1,6 +0,0 @@ -Check [WILDCARD]/dir/mod.ts$2-7.ts -running 1 test from ./dir/mod.ts$2-7.ts -[WILDCARD]/dir/mod.ts$2-7.ts ... ok ([WILDCARD]ms) - -ok | 1 passed | 0 failed ([WILDCARD]ms) - diff --git a/tests/specs/test/doc_only/dir/mod.ts b/tests/specs/test/doc_only/doc_only/mod.ts similarity index 100% rename from tests/specs/test/doc_only/dir/mod.ts rename to tests/specs/test/doc_only/doc_only/mod.ts diff --git a/tests/specs/test/doc_only/main.out b/tests/specs/test/doc_only/main.out index a2eff5e89d34cd..634bd7636d88ab 100644 --- a/tests/specs/test/doc_only/main.out +++ b/tests/specs/test/doc_only/main.out @@ -1,4 +1,6 @@ -Check [WILDCARD]/doc_only/mod.ts$2-5.ts +Check [WILDCARD]/doc_only/mod.ts$2-7.ts +running 1 test from ./doc_only/mod.ts$2-7.ts +[WILDCARD]/doc_only/mod.ts$2-7.ts ... ok ([WILDCARD]ms) -ok | 0 passed | 0 failed ([WILDCARD]) +ok | 1 passed | 0 failed ([WILDCARD]ms) From 2d4ce94076d6a064025b653c9074e08e2df7fa68 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 17 Sep 2024 17:36:07 +0900 Subject: [PATCH 31/31] fix --- tests/specs/test/doc/main.out | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/specs/test/doc/main.out b/tests/specs/test/doc/main.out index b55989f96e4ebb..a255823708427c 100644 --- a/tests/specs/test/doc/main.out +++ b/tests/specs/test/doc/main.out @@ -4,6 +4,6 @@ Check [WILDCARD]/main.ts$14-17.ts Check [WILDCARD]/main.ts$18-21.tsx Check [WILDCARD]/main.ts$30-35.ts error: TS2367 [ERROR]: This comparison appears to be unintentional because the types 'string' and 'number' have no overlap. -console.assert(check() == 42); - ~~~~~~~~~~~~~ - at [WILDCARD]/main.ts$30-35.ts:3:16 + console.assert(check() == 42); + ~~~~~~~~~~~~~ + at [WILDCARD]/main.ts$30-35.ts:3:20