diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b234fea..7bc7dd33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: run: cargo test - name: Pandoc AST fixtures (JSON on disk, no live pandoc) - run: cargo test --features "cli,pandoc" --test pandoc_ast_backend -- --nocapture + run: cargo test --features "cli,pandoc" --test integration pandoc_ast_backend -- --nocapture - name: Colink opt-in without archive fails closed run: | diff --git a/Cargo.toml b/Cargo.toml index da118124..36669c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "snapper-fmt" version = "0.11.7" edition = "2024" +autotests = false rust-version = "1.85" description = "Semantic line break formatter for Org, LaTeX, Markdown, RST, and plaintext" repository = "https://github.com/TurtleTech-ehf/snapper" @@ -160,3 +161,11 @@ pkg-fmt = "txz" [[bench]] name = "reflow_bench" harness = false + +[[test]] +name = "integration" +path = "tests/integration.rs" + +[[test]] +name = "pandoc_parity_speed" +path = "tests/pandoc_parity_speed.rs" diff --git a/cog.toml b/cog.toml index d9592e3c..72983d55 100644 --- a/cog.toml +++ b/cog.toml @@ -7,7 +7,9 @@ tag_prefix = "v" # Bump version everywhere: Cargo.toml drives pyproject.toml via dynamic = ["version"] pre_bump_hooks = [ "sed -i '0,/^version = /s/^version = \".*\"/version = \"{{version}}\"/' Cargo.toml", - "cargo generate-lockfile", + # Only the workspace crates move; generate-lockfile would re-resolve every + # dependency and ship upgrades no test ran against. + "cargo update --workspace", "sed -i 's/^release = \".*\"/release = \"{{version}}\"/' docs/source/conf.py", "sed -i 's/rev: v[0-9][0-9.\\-]*/rev: v{{version}}/' README.md docs/source/howto/vale-integration.rst", "sed -i 's/\"version\": \".*\"/\"version\": \"{{version}}\"/' editors/vscode/package.json editors/obsidian/package.json editors/obsidian/manifest.json editors/word/package.json packages/snapper-wasm/package.json npm/package.json", diff --git a/docs/orgmode/reference/cli.org b/docs/orgmode/reference/cli.org index 20394535..56167744 100644 --- a/docs/orgmode/reference/cli.org +++ b/docs/orgmode/reference/cli.org @@ -290,7 +290,8 @@ Uses =git show REF:path= to retrieve the old version. :END: Watch files and auto-reformat on save. -Accepts file paths or glob patterns. 200ms debounce to avoid reformatting during rapid saves. +Accepts file paths or glob patterns. +200ms debounce to avoid reformatting during rapid saves. Press Ctrl+C to stop. *** =snapper lsp= diff --git a/src/abbreviations.rs b/src/abbreviations.rs index 5fd2f11b..4da8c48a 100644 --- a/src/abbreviations.rs +++ b/src/abbreviations.rs @@ -4,7 +4,9 @@ pub static EN_ABBREVIATIONS: &[&str] = &[ "Mr", "Mrs", "Ms", "Dr", "Prof", "Sr", "Jr", "St", "Rev", "Gen", "Gov", "Sgt", "Cpl", "Pvt", "Capt", "Lt", "Col", "Maj", "Cmdr", "Adm", // Academic / scientific "Fig", "Figs", "Eq", "Eqs", "Ref", "Refs", "Tab", "Sec", "Ch", "Vol", "No", "Nos", "Ed", "Eds", - "Trans", "Dept", "Thm", "Lem", "Prop", "Def", "Cor", "Rem", "Ex", + "Trans", "Dept", "Thm", "Lem", "Prop", "Def", "Cor", "Rem", + "Ex", // The same, lowercase in running text + "fig", "figs", "eq", "eqs", "ref", "refs", "tab", "sec", "ch", "vol", "nos", "p", // Latin abbreviations "al", "approx", "ca", "cf", "etc", "et", "ibid", "viz", // Common "vs", "misc", "est", "govt", "dept", "univ", "inc", "Inc", "corp", "Corp", "ltd", "Ltd", "Ave", diff --git a/src/check.rs b/src/check.rs index 867084dd..d5ac0da7 100644 --- a/src/check.rs +++ b/src/check.rs @@ -345,9 +345,8 @@ mod tests { #[test] fn long_advisory_needs_clause_boundary() { - let long_with_comma = format!( - "The quick brown fox jumps over the lazy dog, then continues running across a very long meadow without pausing for breath at all today.\n" - ); + let long_with_comma = + "The quick brown fox jumps over the lazy dog, then continues running across a very long meadow without pausing for breath at all today.\n".to_string(); assert!( long_with_comma.trim_end().chars().count() > DEFAULT_LONG_THRESHOLD, "fixture must exceed the default long threshold" diff --git a/src/parser/latex.rs b/src/parser/latex.rs index e41d4930..8848e467 100644 --- a/src/parser/latex.rs +++ b/src/parser/latex.rs @@ -4740,7 +4740,7 @@ Some text. // Whole-line prefix: leftover scan `from` can sit after another // leftover on the same line. `\lstinline` must stay Prose so // `See \verb|x| here.` is not the only mid-line case. - let after_other = concat!("\\py{print(1)} \\lstinline|print(1)|\n",); + let after_other = "\\py{print(1)} \\lstinline|print(1)|\n"; let after_other_regions = LatexParser::default().parse(after_other); assert!( after_other_regions.iter().any(|r| matches!( @@ -6120,7 +6120,8 @@ Some text. Region::Code { body, footer, .. } => Some((body.as_str(), footer.as_str())), _ => None, }); - let (body, footer) = code.expect(&format!("lstlisting must be Code, got: {regions:?}")); + let (body, footer) = + code.unwrap_or_else(|| panic!("lstlisting must be Code, got: {regions:?}")); assert!( body.contains("print(1)"), "listing body must keep source, got body={body:?} regions={regions:?}" @@ -6159,7 +6160,8 @@ Some text. Region::Code { body, footer, .. } => Some((body.as_str(), footer.as_str())), _ => None, }); - let (body, footer) = code.expect(&format!("verbatim must be Code, got: {regions:?}")); + let (body, footer) = + code.unwrap_or_else(|| panic!("verbatim must be Code, got: {regions:?}")); assert!( body.contains("still body"), "inner \\end must not close the outer verbatim; still body stays in the listing, got body={body:?} regions={regions:?}" @@ -6196,7 +6198,8 @@ Some text. Region::Code { body, footer, .. } => Some((body.as_str(), footer.as_str())), _ => None, }); - let (body, footer) = code.expect(&format!("lstlisting must be Code, got: {regions:?}")); + let (body, footer) = + code.unwrap_or_else(|| panic!("lstlisting must be Code, got: {regions:?}")); assert!( body.contains("print(1)"), "listing body must keep source before %, got body={body:?} regions={regions:?}" @@ -7646,9 +7649,8 @@ Some text. Region::Code { body, footer, .. } => Some((body.as_str(), footer.as_str())), _ => None, }); - let (raw_body, raw_footer) = raw_code.expect(&format!( - "lstlisting* must stay Code on %, got: {raw_regions:?}" - )); + let (raw_body, raw_footer) = raw_code + .unwrap_or_else(|| panic!("lstlisting* must stay Code on %, got: {raw_regions:?}")); assert!( raw_body.contains("print(1)"), "lstlisting* raw scan must keep source before %, got body={raw_body:?}" diff --git a/src/parser/markdown.rs b/src/parser/markdown.rs index 34b5a90d..b334b277 100644 --- a/src/parser/markdown.rs +++ b/src/parser/markdown.rs @@ -5740,7 +5740,8 @@ mod tests { Region::Structure(s) if s.contains("\nKeep this. Exactly here.\n\nFinal thing. Last sentence.\n"; - let out = pipe_stdin(input, &["--format", "markdown"]); - let result = String::from_utf8(out.stdout).unwrap(); - assert!(result.contains("Hello world.\nGoodbye world.\n")); - assert!(result.contains("Keep this. Exactly here.\n")); - assert!(result.contains("Final thing.\nLast sentence.")); -} - -#[test] -fn pragma_off_on_plaintext() { - let input = "Hello world. Goodbye world.\nsnapper:off\nKeep this. Exactly here.\nsnapper:on\nFinal thing. Last sentence.\n"; - let out = pipe_stdin(input, &["--format", "plaintext"]); - let result = String::from_utf8(out.stdout).unwrap(); - assert!(result.contains("Hello world.\nGoodbye world.\n")); - assert!(result.contains("Keep this. Exactly here.\n")); - assert!(result.contains("Final thing.\nLast sentence.\n")); -} - -// ---- FEATURE 5: --range ---- - -#[test] -fn range_formats_only_specified_lines() { - let input = "Line one. Stay same.\nLine two. Should split. Into two.\nLine three. Stay same.\n"; - let out = pipe_stdin(input, &["--range", "2:2"]); - let result = String::from_utf8(out.stdout).unwrap(); - // Line 1 unchanged - assert!(result.starts_with("Line one. Stay same.\n")); - // Line 2 split - assert!(result.contains("Line two.\nShould split.\nInto two.\n")); - // Line 3 unchanged - assert!(result.ends_with("Line three. Stay same.\n")); -} - -#[test] -fn range_preserves_outside_lines_exactly() { - let tmp = tempfile::NamedTempFile::new().unwrap(); - fs::write( - tmp.path(), - "Keep. This line.\nReformat. This line. Please.\nKeep. This too.\n", - ) - .unwrap(); - let output = snapper_binary() - .args([ - "--native", - "--format", - "plaintext", - "--range", - "2:2", - &tmp.path().to_string_lossy(), - ]) - .output() - .expect("failed to run"); - let result = String::from_utf8(output.stdout).unwrap(); - assert!(result.starts_with("Keep. This line.\n")); - assert!(result.contains("Reformat.\nThis line.\nPlease.\n")); - assert!(result.ends_with("Keep. This too.\n")); -} - -// ---- FEATURE 6: --output-format json/sarif ---- - -#[test] -fn check_json_output() { - let output = snapper_binary() - .args([ - "--native", - "--check", - "--output-format", - "json", - "--format", - "org", - &fixture_path("sample.org"), - ]) - .output() - .expect("failed to run"); - let stdout = String::from_utf8(output.stdout).unwrap(); - let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("invalid JSON"); - assert!(parsed.is_array()); - let arr = parsed.as_array().unwrap(); - assert_eq!(arr.len(), 1); - assert!(arr[0]["file"].as_str().unwrap().contains("sample.org")); -} - -#[test] -fn check_sarif_output() { - let output = snapper_binary() - .args([ - "--native", - "--check", - "--output-format", - "sarif", - "--format", - "org", - &fixture_path("sample.org"), - ]) - .output() - .expect("failed to run"); - let stdout = String::from_utf8(output.stdout).unwrap(); - let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("invalid SARIF"); - assert_eq!(parsed["version"], "2.1.0"); - let results = &parsed["runs"][0]["results"]; - assert!(results.is_array()); - assert!(!results.as_array().unwrap().is_empty()); -} - -// ---- Golden master: edge cases ---- - -#[test] -fn edge_cases_org() { - let actual = run_format("org", &fixture_path("edge_cases.org")); - let expected = fs::read_to_string(fixture_path("expected_edge_cases.org")).unwrap(); - pretty_assertions::assert_eq!(actual, expected); -} - -#[test] -fn edge_cases_plaintext() { - let actual = run_format("plaintext", &fixture_path("edge_cases.txt")); - let expected = fs::read_to_string(fixture_path("expected_edge_cases.txt")).unwrap(); - pretty_assertions::assert_eq!(actual, expected); -} - -#[test] -fn idempotent_edge_cases_org() { - let first = run_format("org", &fixture_path("edge_cases.org")); - let tmp = tempfile::NamedTempFile::new().unwrap(); - fs::write(tmp.path(), &first).unwrap(); - let second = run_format("org", &tmp.path().to_string_lossy()); - pretty_assertions::assert_eq!(first, second, "edge cases org reflow must be idempotent"); -} - -#[test] -fn idempotent_edge_cases_plaintext() { - let first = run_format("plaintext", &fixture_path("edge_cases.txt")); - let tmp = tempfile::NamedTempFile::new().unwrap(); - fs::write(tmp.path(), &first).unwrap(); - let second = run_format("plaintext", &tmp.path().to_string_lossy()); - pretty_assertions::assert_eq!( - first, - second, - "edge cases plaintext reflow must be idempotent" - ); -} - -// ---- FEATURE 7: snapper init ---- - -#[test] -fn init_dry_run_shows_config() { - let output = snapper_binary() - .args(["init", "--dry-run"]) - .output() - .expect("failed to run"); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains(".snapperrc.toml")); - assert!(stderr.contains("format =")); - assert!(stderr.contains("pre-commit")); - assert!(stderr.contains("apheleia")); -} - -#[test] -fn config_lang_applies_to_stdin() { - let dir = tempfile::tempdir().unwrap(); - fs::write(dir.path().join(".snapperrc.toml"), "lang = \"de\"\n").unwrap(); - let out = pipe_stdin_in_dir("z.B. Das ist ein Satz. Noch einer.\n", &[], dir.path()); - assert!(out.status.success()); - let result = String::from_utf8(out.stdout).unwrap(); - assert_eq!(result, "z.B. Das ist ein Satz.\nNoch einer.\n"); -} - -#[test] -fn config_default_format_applies_to_stdin() { - let dir = tempfile::tempdir().unwrap(); - fs::write(dir.path().join(".snapperrc.toml"), "format = \"org\"\n").unwrap(); - let out = pipe_stdin_in_dir("* Heading\nSentence one. Sentence two.\n", &[], dir.path()); - assert!(out.status.success()); - let result = String::from_utf8(out.stdout).unwrap(); - assert!(result.starts_with("* Heading\n")); - assert!(result.contains("Sentence one.\n")); - assert!(result.contains("Sentence two.\n")); -} - -#[test] -fn config_ignore_skips_check_mode() { - let dir = tempfile::tempdir().unwrap(); - fs::write(dir.path().join(".snapperrc.toml"), "ignore = [\"*.md\"]\n").unwrap(); - fs::write(dir.path().join("draft.md"), "Sentence one. Sentence two.\n").unwrap(); - let output = snapper_binary() - .current_dir(dir.path()) - .args(["--native", "--check", "draft.md"]) - .output() - .expect("failed to run snapper"); - assert!(output.status.success()); - assert!(String::from_utf8(output.stderr).unwrap().is_empty()); -} - -#[test] -fn config_per_format_width_applies() { - let dir = tempfile::tempdir().unwrap(); - fs::write( - dir.path().join(".snapperrc.toml"), - "max_width = 80\n[plaintext]\nmax_width = 12\n", - ) - .unwrap(); - fs::write( - dir.path().join("draft.txt"), - "Alpha beta gamma delta epsilon zeta.\n", - ) - .unwrap(); - let output = snapper_binary() - .current_dir(dir.path()) - .args(["--native", "draft.txt"]) - .output() - .expect("failed to run snapper"); - assert!(output.status.success()); - let result = String::from_utf8(output.stdout).unwrap(); - assert_eq!(result, "Alpha beta\ngamma delta\nepsilon\nzeta.\n"); -} - -#[test] -fn config_latex_envs_and_commands_apply() { - let dir = tempfile::tempdir().unwrap(); - fs::write( - dir.path().join(".snapperrc.toml"), - "[latex]\nverbatim_envs = [\"Verbatim\"]\nstructure_envs = [\"algorithm\", \"comment\"]\nverbatim_commands = [\"Verb\"]\n", - ) - .unwrap(); - let input = "\\begin{document}\n\\begin{algorithm}\nFirst step. Second step.\n\\end{algorithm}\nUse \\Verb|a.b! c| here. Next sentence.\n\\end{document}\n"; - let out = pipe_stdin_in_dir(input, &["--format", "latex"], dir.path()); - assert!( - out.status.success(), - "stderr={}", - String::from_utf8_lossy(&out.stderr) - ); - let result = String::from_utf8(out.stdout).unwrap(); - assert!( - result.contains("First step. Second step."), - "algorithm body must not reflow, got:\n{result}" - ); - assert!( - !result.contains("First step.\nSecond step."), - "algorithm must stay one source line, got:\n{result}" - ); - assert!( - result.contains("Use \\Verb|a.b! c| here.\nNext sentence."), - "configured Verb must tokenize like verb, got:\n{result}" - ); -} - -#[test] -fn markdown_numbered_atx_heading_not_split() { - // snapper-25kc / rtrash README regression - let input = - "### 1. `cargo binstall` (preferred binary install)\n\nBody one. Body two more words.\n"; - let out = pipe_stdin(input, &["--format", "markdown"]); - assert!( - out.status.success(), - "stderr={}", - String::from_utf8_lossy(&out.stderr) - ); - let result = String::from_utf8(out.stdout).unwrap(); - assert!( - result.starts_with("### 1. `cargo binstall` (preferred binary install)\n"), - "heading must stay one line, got:\n{result}" - ); - assert!( - !result.contains("### 1.\n`"), - "must not orphan ### 1. before title:\n{result}" - ); -} - -#[test] -fn markdown_setext_heading_not_collapsed() { - let input = "Setext Title With Period. Still Title\n=====================================\n\nBody after. Second body.\n"; - let out = pipe_stdin(input, &["--format", "markdown"]); - assert!( - out.status.success(), - "stderr={}", - String::from_utf8_lossy(&out.stderr) - ); - let result = String::from_utf8(out.stdout).unwrap(); - assert!( - result.starts_with( - "Setext Title With Period. Still Title\n=====================================\n" - ), - "setext title+underline must stay intact, got:\n{result}" - ); - assert!( - !result.contains("Still Title ====="), - "must not glue underline onto title:\n{result}" - ); -} +//! Every integration test in one binary. A file under tests/ built as its +//! own crate links the whole library, so `cargo test --release` would link +//! some 135 optimised binaries at once and can exhaust memory (#504); one +//! binary links the library once. `pandoc_parity_speed` keeps its own +//! process because it sets the pandoc cache variables other tests read. + +#[path = "check_line_diagnostics.rs"] +mod check_line_diagnostics; +#[path = "code_block_format.rs"] +mod code_block_format; +#[path = "code_block_reflow.rs"] +mod code_block_reflow; +#[path = "latex_enumerate_hang.rs"] +mod latex_enumerate_hang; +#[path = "latex_figure_table_captions.rs"] +mod latex_figure_table_captions; +#[path = "latex_ieeeeqnarray_xltabular.rs"] +mod latex_ieeeeqnarray_xltabular; +#[path = "latex_leftover_cmds.rs"] +mod latex_leftover_cmds; +#[path = "latex_more_envs.rs"] +mod latex_more_envs; +#[path = "latex_nicetabular.rs"] +mod latex_nicetabular; +#[path = "latex_non_prose_envs.rs"] +mod latex_non_prose_envs; +#[path = "latex_section_short_title.rs"] +mod latex_section_short_title; +#[path = "latex_tikz_pgf_envs.rs"] +mod latex_tikz_pgf_envs; +#[path = "latex_verbatim_envs.rs"] +mod latex_verbatim_envs; +#[path = "md_atx_heading_indent.rs"] +mod md_atx_heading_indent; +#[path = "md_compact_dl_markers.rs"] +mod md_compact_dl_markers; +#[path = "md_compact_dl_terms.rs"] +mod md_compact_dl_terms; +#[path = "md_definition_lists.rs"] +mod md_definition_lists; +#[path = "md_dollar_period.rs"] +mod md_dollar_period; +#[path = "md_empty_atx_heading.rs"] +mod md_empty_atx_heading; +#[path = "md_empty_list_item.rs"] +mod md_empty_list_item; +#[path = "md_example_list.rs"] +mod md_example_list; +#[path = "md_four_space_nested_list.rs"] +mod md_four_space_nested_list; +#[path = "md_gfm_alerts.rs"] +mod md_gfm_alerts; +#[path = "md_html_comment_indent.rs"] +mod md_html_comment_indent; +#[path = "md_html_type6_close.rs"] +mod md_html_type6_close; +#[path = "md_html_type7_close.rs"] +mod md_html_type7_close; +#[path = "md_light_table_interrupt.rs"] +mod md_light_table_interrupt; +#[path = "md_link_ref_dest_next_line.rs"] +mod md_link_ref_dest_next_line; +#[path = "md_link_ref_footnotes.rs"] +mod md_link_ref_footnotes; +#[path = "md_link_ref_title_next_line.rs"] +mod md_link_ref_title_next_line; +#[path = "md_link_tail_sentence.rs"] +mod md_link_tail_sentence; +#[path = "md_list_hang_code.rs"] +mod md_list_hang_code; +#[path = "md_list_item_lrd.rs"] +mod md_list_item_lrd; +#[path = "md_lrd_no_interrupt.rs"] +mod md_lrd_no_interrupt; +#[path = "md_multiline_setext.rs"] +mod md_multiline_setext; +#[path = "md_ol_opener_list_item.rs"] +mod md_ol_opener_list_item; +#[path = "md_ol_start_setext.rs"] +mod md_ol_start_setext; +#[path = "md_ol_ten_digit.rs"] +mod md_ol_ten_digit; +#[path = "md_pandoc_citation.rs"] +mod md_pandoc_citation; +#[path = "md_pandoc_line_block.rs"] +mod md_pandoc_line_block; +#[path = "md_quoted_definition_lists.rs"] +mod md_quoted_definition_lists; +#[path = "md_quoted_empty_list_item.rs"] +mod md_quoted_empty_list_item; +#[path = "md_quoted_footnote.rs"] +mod md_quoted_footnote; +#[path = "md_quoted_gfm_tables.rs"] +mod md_quoted_gfm_tables; +#[path = "md_quoted_html_blocks.rs"] +mod md_quoted_html_blocks; +#[path = "md_quoted_indented_code.rs"] +mod md_quoted_indented_code; +#[path = "md_quoted_lrd.rs"] +mod md_quoted_lrd; +#[path = "md_ref_link_interior_punct.rs"] +mod md_ref_link_interior_punct; +#[path = "md_shortcut_ref_label.rs"] +mod md_shortcut_ref_label; +#[path = "md_spanning_lrd_title.rs"] +mod md_spanning_lrd_title; +#[path = "md_yaml_front_matter.rs"] +mod md_yaml_front_matter; +#[path = "org_angle_links.rs"] +mod org_angle_links; +#[path = "org_brace_subscript.rs"] +mod org_brace_subscript; +#[path = "org_caption.rs"] +mod org_caption; +#[path = "org_clock_line.rs"] +mod org_clock_line; +#[path = "org_diary_sexp.rs"] +mod org_diary_sexp; +#[path = "org_dollar_space.rs"] +mod org_dollar_space; +#[path = "org_drawer_word_names.rs"] +mod org_drawer_word_names; +#[path = "org_dynamic_block.rs"] +mod org_dynamic_block; +#[path = "org_empty_list_item.rs"] +mod org_empty_list_item; +#[path = "org_export_snippet.rs"] +mod org_export_snippet; +#[path = "org_export_snippet_at.rs"] +mod org_export_snippet_at; +#[path = "org_file_token_punct.rs"] +mod org_file_token_punct; +#[path = "org_footnote_definitions.rs"] +mod org_footnote_definitions; +#[path = "org_hash_comment.rs"] +mod org_hash_comment; +#[path = "org_incomplete_begin.rs"] +mod org_incomplete_begin; +#[path = "org_inline_footnotes.rs"] +mod org_inline_footnotes; +#[path = "org_inline_src.rs"] +mod org_inline_src; +#[path = "org_item_tag.rs"] +mod org_item_tag; +#[path = "org_keyword_re.rs"] +mod org_keyword_re; +#[path = "org_latex_env_eol_closer.rs"] +mod org_latex_env_eol_closer; +#[path = "org_latex_fragment.rs"] +mod org_latex_fragment; +#[path = "org_line_breaks.rs"] +mod org_line_breaks; +#[path = "org_macros.rs"] +mod org_macros; +#[path = "org_planning_case.rs"] +mod org_planning_case; +#[path = "org_radio_targets.rs"] +mod org_radio_targets; +#[path = "org_special_block.rs"] +mod org_special_block; +#[path = "org_table_rule.rs"] +mod org_table_rule; +#[path = "org_timestamps.rs"] +mod org_timestamps; +#[path = "org_unicode_src_subscript.rs"] +mod org_unicode_src_subscript; +#[path = "org_unmatched_display_math.rs"] +mod org_unmatched_display_math; +#[path = "org_unmatched_drawer.rs"] +mod org_unmatched_drawer; +#[path = "org_unmatched_dynamic_block.rs"] +mod org_unmatched_dynamic_block; +#[path = "org_unmatched_opaque.rs"] +mod org_unmatched_opaque; +#[path = "org_verse.rs"] +mod org_verse; +#[path = "org_wrap_created_fn.rs"] +mod org_wrap_created_fn; +#[path = "org_wrap_created_link.rs"] +mod org_wrap_created_link; +#[path = "org_wrap_file_token.rs"] +mod org_wrap_file_token; +#[path = "pandoc_ast_backend.rs"] +mod pandoc_ast_backend; +#[path = "rst_abbrev_list_continuation.rs"] +mod rst_abbrev_list_continuation; +#[path = "rst_anonymous_targets.rs"] +mod rst_anonymous_targets; +#[path = "rst_bibliographic_abstract.rs"] +mod rst_bibliographic_abstract; +#[path = "rst_comment_blank.rs"] +mod rst_comment_blank; +#[path = "rst_compact_listlike_paragraph.rs"] +mod rst_compact_listlike_paragraph; +#[path = "rst_compact_note_directive.rs"] +mod rst_compact_note_directive; +#[path = "rst_container_directives.rs"] +mod rst_container_directives; +#[path = "rst_csv_table_title.rs"] +mod rst_csv_table_title; +#[path = "rst_definition_after_directive.rs"] +mod rst_definition_after_directive; +#[path = "rst_directive_title.rs"] +mod rst_directive_title; +#[path = "rst_doctest_blocks.rs"] +mod rst_doctest_blocks; +#[path = "rst_empty_doctest.rs"] +mod rst_empty_doctest; +#[path = "rst_empty_explicit_comment.rs"] +mod rst_empty_explicit_comment; +#[path = "rst_empty_field_list.rs"] +mod rst_empty_field_list; +#[path = "rst_empty_list_item.rs"] +mod rst_empty_list_item; +#[path = "rst_enumerators.rs"] +mod rst_enumerators; +#[path = "rst_footnotes_citations.rs"] +mod rst_footnotes_citations; +#[path = "rst_header_footer.rs"] +mod rst_header_footer; +#[path = "rst_interior_colon_field.rs"] +mod rst_interior_colon_field; +#[path = "rst_jinja_statements.rs"] +mod rst_jinja_statements; +#[path = "rst_line_block_directive.rs"] +mod rst_line_block_directive; +#[path = "rst_line_blocks.rs"] +mod rst_line_blocks; +#[path = "rst_literal_list_continuation.rs"] +mod rst_literal_list_continuation; +#[path = "rst_meta.rs"] +mod rst_meta; +#[path = "rst_nested_list_continuation.rs"] +mod rst_nested_list_continuation; +#[path = "rst_option_lists.rs"] +mod rst_option_lists; +#[path = "rst_overindented_list_continuation.rs"] +mod rst_overindented_list_continuation; +#[path = "rst_paren_list_continuation.rs"] +mod rst_paren_list_continuation; +#[path = "rst_parsed_literal.rs"] +mod rst_parsed_literal; +#[path = "rst_quoted_list_continuation.rs"] +mod rst_quoted_list_continuation; +#[path = "rst_quoted_literals.rs"] +mod rst_quoted_literals; +#[path = "rst_role_list_continuation.rs"] +mod rst_role_list_continuation; +#[path = "rst_same_line_note.rs"] +mod rst_same_line_note; +#[path = "rst_section_adornment.rs"] +mod rst_section_adornment; +#[path = "rst_simple_table_interior_blank.rs"] +mod rst_simple_table_interior_blank; +#[path = "rst_simple_table_top.rs"] +mod rst_simple_table_top; +#[path = "rst_substitution_refs.rs"] +mod rst_substitution_refs; +#[path = "rst_substitution_replace.rs"] +mod rst_substitution_replace; +#[path = "rst_two_space_bullet_marker.rs"] +mod rst_two_space_bullet_marker; +#[path = "safety_props.rs"] +mod safety_props; +#[path = "safety_splice.rs"] +mod safety_splice; +#[path = "same_line_lowercase_proper_noun.rs"] +mod same_line_lowercase_proper_noun; +#[path = "sentence_delim_props.rs"] +mod sentence_delim_props; +#[path = "snapper_19jf.rs"] +mod snapper_19jf; diff --git a/tests/latex_verbatim_envs.rs b/tests/latex_verbatim_envs.rs index 5270cffd..373f94fe 100644 --- a/tests/latex_verbatim_envs.rs +++ b/tests/latex_verbatim_envs.rs @@ -3095,7 +3095,7 @@ fn inputminted_fixture_does_not_join_following_prose() { /// optional `[...]` stay atomic. inputminted / lstinputlisting /// unchanged. #[test] -fn verbatiminput_fixture_does_not_join_following_prose() { +fn verbatim_input_fancyvrb_fixture_does_not_join_following_prose() { let input = concat!( "Before. Next.\n", "\\VerbatimInput{foo.py}\n", @@ -3481,107 +3481,108 @@ fn piton_env_and_pipe_cmd_fixture_is_code_and_does_not_reflow() { verb_out.contains("Use \\verb|a.b! c| here.\nNext sentence."), "prose after \\verb must still split, got:\n{verb_out}" ); - /// Ticket fixture (GitHub #398): tools/verbatim.sty `\verbatiminput{file}` - /// stays one atomic command. Following flush `After.` does not join - /// the command line. `After.` / `Next.` still split. lstinputlisting / - /// inputminted unchanged. - #[test] - fn verbatiminput_fixture_does_not_join_following_prose() { - let input = concat!( - "Before. Next.\n", - "\\verbatiminput{foo.py}\n", - "After. Next.\n", - ); - let regions = LatexParser::default().parse(input); - assert!( - regions.iter().any(|r| matches!( - r, - Region::Structure(s) if s.contains(r"\verbatiminput{foo.py}") - )), - "verbatiminput must stay one Structure command, got: {regions:?}" - ); - assert!( - !regions.iter().any(|r| matches!( - r, - Region::Prose(p) if p.contains(r"\verbatiminput{foo.py}") - )), - "verbatiminput must not leak into Prose, got: {regions:?}" - ); - assert!( - regions.iter().any(|r| matches!( - r, - Region::Prose(p) if p.contains("After.") && p.contains("Next.") - )), - "After. / Next. must stay Prose, got: {regions:?}" - ); - let out = format_text(input, &latex_cfg()).unwrap(); - assert!( - out.contains("\\verbatiminput{foo.py}\n"), - "verbatiminput must stay one atomic command, got:\n{out}" - ); - assert!( - !out.contains("\\verbatiminput{foo.py} After."), - "following flush prose must not join the command line, got:\n{out}" - ); - assert!( - out.contains("Before.\nNext."), - "prose before verbatiminput must still split, got:\n{out}" - ); - assert!( - out.contains("After.\nNext."), - "prose after verbatiminput must still split, got:\n{out}" - ); - assert_eq!(format_text(&out, &latex_cfg()).unwrap(), out); +} - let star = concat!( - "Before. Next.\n", - "\\verbatiminput*{foo.py}\n", - "After. Next.\n", - ); - let star_out = format_text(star, &latex_cfg()).unwrap(); - assert!( - star_out.contains("\\verbatiminput*{foo.py}\n"), - "verbatiminput* must stay one atomic command, got:\n{star_out}" - ); - assert!( - !star_out.contains("\\verbatiminput*{foo.py} After."), - "starred verbatiminput must not join following prose, got:\n{star_out}" - ); - assert!( - star_out.contains("After.\nNext."), - "prose after starred verbatiminput must still split, got:\n{star_out}" - ); +/// Ticket fixture (GitHub #398): tools/verbatim.sty `\verbatiminput{file}` +/// stays one atomic command. Following flush `After.` does not join +/// the command line. `After.` / `Next.` still split. lstinputlisting / +/// inputminted unchanged. +#[test] +fn verbatiminput_fixture_does_not_join_following_prose() { + let input = concat!( + "Before. Next.\n", + "\\verbatiminput{foo.py}\n", + "After. Next.\n", + ); + let regions = LatexParser::default().parse(input); + assert!( + regions.iter().any(|r| matches!( + r, + Region::Structure(s) if s.contains(r"\verbatiminput{foo.py}") + )), + "verbatiminput must stay one Structure command, got: {regions:?}" + ); + assert!( + !regions.iter().any(|r| matches!( + r, + Region::Prose(p) if p.contains(r"\verbatiminput{foo.py}") + )), + "verbatiminput must not leak into Prose, got: {regions:?}" + ); + assert!( + regions.iter().any(|r| matches!( + r, + Region::Prose(p) if p.contains("After.") && p.contains("Next.") + )), + "After. / Next. must stay Prose, got: {regions:?}" + ); + let out = format_text(input, &latex_cfg()).unwrap(); + assert!( + out.contains("\\verbatiminput{foo.py}\n"), + "verbatiminput must stay one atomic command, got:\n{out}" + ); + assert!( + !out.contains("\\verbatiminput{foo.py} After."), + "following flush prose must not join the command line, got:\n{out}" + ); + assert!( + out.contains("Before.\nNext."), + "prose before verbatiminput must still split, got:\n{out}" + ); + assert!( + out.contains("After.\nNext."), + "prose after verbatiminput must still split, got:\n{out}" + ); + assert_eq!(format_text(&out, &latex_cfg()).unwrap(), out); - let lst = concat!( - "Before. Next.\n", - "\\lstinputlisting{foo.py}\n", - "After. Next.\n", - ); - let lst_out = format_text(lst, &latex_cfg()).unwrap(); - assert!( - lst_out.contains("\\lstinputlisting{foo.py}\n"), - "lstinputlisting must stay unchanged, got:\n{lst_out}" - ); - assert!( - !lst_out.contains("\\lstinputlisting{foo.py} After."), - "lstinputlisting must not join following prose, got:\n{lst_out}" - ); + let star = concat!( + "Before. Next.\n", + "\\verbatiminput*{foo.py}\n", + "After. Next.\n", + ); + let star_out = format_text(star, &latex_cfg()).unwrap(); + assert!( + star_out.contains("\\verbatiminput*{foo.py}\n"), + "verbatiminput* must stay one atomic command, got:\n{star_out}" + ); + assert!( + !star_out.contains("\\verbatiminput*{foo.py} After."), + "starred verbatiminput must not join following prose, got:\n{star_out}" + ); + assert!( + star_out.contains("After.\nNext."), + "prose after starred verbatiminput must still split, got:\n{star_out}" + ); - let minted = concat!( - "Before. Next.\n", - "\\inputminted{python}{foo.py}\n", - "After. Next.\n", - ); - let minted_out = format_text(minted, &latex_cfg()).unwrap(); - assert!( - minted_out.contains("\\inputminted{python}{foo.py}\n"), - "inputminted must stay unchanged, got:\n{minted_out}" - ); - assert!( - !minted_out.contains("\\inputminted{python}{foo.py} After."), - "inputminted must not join following prose, got:\n{minted_out}" - ); - } + let lst = concat!( + "Before. Next.\n", + "\\lstinputlisting{foo.py}\n", + "After. Next.\n", + ); + let lst_out = format_text(lst, &latex_cfg()).unwrap(); + assert!( + lst_out.contains("\\lstinputlisting{foo.py}\n"), + "lstinputlisting must stay unchanged, got:\n{lst_out}" + ); + assert!( + !lst_out.contains("\\lstinputlisting{foo.py} After."), + "lstinputlisting must not join following prose, got:\n{lst_out}" + ); + + let minted = concat!( + "Before. Next.\n", + "\\inputminted{python}{foo.py}\n", + "After. Next.\n", + ); + let minted_out = format_text(minted, &latex_cfg()).unwrap(); + assert!( + minted_out.contains("\\inputminted{python}{foo.py}\n"), + "inputminted must stay unchanged, got:\n{minted_out}" + ); + assert!( + !minted_out.contains("\\inputminted{python}{foo.py} After."), + "inputminted must not join following prose, got:\n{minted_out}" + ); } /// Ticket fixture (GitHub #400): tcolorbox `\\tcbinputlisting`. @@ -5276,7 +5277,7 @@ fn verb_span_leftover_cmds_fixture_does_not_join_following_prose() { "mid-sentence verb span must stay protected and still split, got:\n{mid_out}" ); - let after_other = concat!("\\py{print(1)} \\lstinline|print(1)|\n",); + let after_other = "\\py{print(1)} \\lstinline|print(1)|\n"; let after_other_regions = LatexParser::default().parse(after_other); assert!( after_other_regions.iter().any(|r| matches!( diff --git a/tests/md_html_type6_close.rs b/tests/md_html_type6_close.rs index 1eb05dc4..076de659 100644 --- a/tests/md_html_type6_close.rs +++ b/tests/md_html_type6_close.rs @@ -46,7 +46,7 @@ fn closed_div_is_structure_through_close() { Region::Structure(s) if s.contains("
") => Some(s.as_str()), _ => None, }); - let div = div.expect(&format!("div block must be Structure, got {regions:?}")); + let div = div.unwrap_or_else(|| panic!("div block must be Structure, got {regions:?}")); assert!(div.contains("
"), "{div}"); assert!(div.contains("First. Second."), "{div}"); assert!(div.contains("
"), "{div}"); diff --git a/tests/md_html_type7_close.rs b/tests/md_html_type7_close.rs index e2d51d42..4dc92097 100644 --- a/tests/md_html_type7_close.rs +++ b/tests/md_html_type7_close.rs @@ -44,7 +44,7 @@ fn closed_span_is_structure_through_close() { Region::Structure(s) if s.contains(" Some(s.as_str()), _ => None, }); - let span = span.expect(&format!("span block must be Structure, got {regions:?}")); + let span = span.unwrap_or_else(|| panic!("span block must be Structure, got {regions:?}")); assert!(span.contains(""), "{span}"); assert!(span.contains("First. Second."), "{span}"); assert!(span.contains(""), "{span}"); @@ -102,7 +102,7 @@ fn type6_closed_div_runs_to_a_blank_line() { Region::Structure(s) if s.contains("
") => Some(s.as_str()), _ => None, }); - let div = div.expect(&format!("div block must be Structure, got {regions:?}")); + let div = div.unwrap_or_else(|| panic!("div block must be Structure, got {regions:?}")); assert!(div.contains("
"), "{div}"); assert!( div.contains("After html. Next."), diff --git a/tests/md_ol_opener_list_item.rs b/tests/md_ol_opener_list_item.rs index 49f0e973..d133d848 100644 --- a/tests/md_ol_opener_list_item.rs +++ b/tests/md_ol_opener_list_item.rs @@ -3,10 +3,10 @@ use snapper_fmt::parser::markdown::MarkdownParser; use snapper_fmt::parser::{FormatParser, Region}; use snapper_fmt::{FormatConfig, format_text}; -/// snapper-9dc1: Markdown `* 0. A.` is one list item. pulldown treats -/// `0. A.` as a nested ordered list. A sembr hang after `0.` is a -/// continuation paragraph, so the HTML oracle vetoes. Keep the opener -/// with the next sentence; do not invent a nested `0. ` Structure. +// snapper-9dc1: Markdown `* 0. A.` is one list item. pulldown treats +// `0. A.` as a nested ordered list. A sembr hang after `0.` is a +// continuation paragraph, so the HTML oracle vetoes. Keep the opener +// with the next sentence; do not invent a nested `0. ` Structure. fn md_cfg() -> FormatConfig { FormatConfig { diff --git a/tests/md_ol_ten_digit.rs b/tests/md_ol_ten_digit.rs index af3561da..c3df4348 100644 --- a/tests/md_ol_ten_digit.rs +++ b/tests/md_ol_ten_digit.rs @@ -3,9 +3,9 @@ use snapper_fmt::parser::markdown::MarkdownParser; use snapper_fmt::parser::{FormatParser, Region}; use snapper_fmt::{FormatConfig, format_text}; -/// snapper-sp7k / GitHub #172: CommonMark 0.31.2 sec 5.2 and pulldown -/// `scan_list_marker_with_indent` cap ordered markers at 9 digits. -/// `1234567890.` is prose, not a list. Oracle stays one `

`. +// snapper-sp7k / GitHub #172: CommonMark 0.31.2 sec 5.2 and pulldown +// `scan_list_marker_with_indent` cap ordered markers at 9 digits. +// `1234567890.` is prose, not a list. Oracle stays one `

`. fn md_cfg() -> FormatConfig { FormatConfig { diff --git a/tests/md_yaml_front_matter.rs b/tests/md_yaml_front_matter.rs index 551b39e5..008e2079 100644 --- a/tests/md_yaml_front_matter.rs +++ b/tests/md_yaml_front_matter.rs @@ -3,9 +3,9 @@ use snapper_fmt::parser::markdown::MarkdownParser; use snapper_fmt::parser::{FormatParser, Region}; use snapper_fmt::{FormatConfig, format_text}; -/// snapper-4why: pulldown `scan_metadata_block` / `scan_closing_metadata_block`. -/// `---` opens only when the next line is neither blank nor the closer. -/// YAML closer is `---` or `...`. Blank after `---` is a thematic break. +// snapper-4why: pulldown `scan_metadata_block` / `scan_closing_metadata_block`. +// `---` opens only when the next line is neither blank nor the closer. +// YAML closer is `---` or `...`. Blank after `---` is a thematic break. fn md_cfg() -> FormatConfig { FormatConfig { diff --git a/tests/org_incomplete_begin.rs b/tests/org_incomplete_begin.rs index e9d26e92..9246fba6 100644 --- a/tests/org_incomplete_begin.rs +++ b/tests/org_incomplete_begin.rs @@ -68,7 +68,7 @@ fn assert_after_next_prose_and_split(input: &str, out: &str, regions: &[Region]) !out.contains("After the env. Next."), "fused trailing prose must not survive, got:\n{out}" ); - assert_eq!(format_text(&out, &org_cfg()).unwrap(), out); + assert_eq!(format_text(out, &org_cfg()).unwrap(), out); let guarded = FormatConfig { format: Format::Org, diff --git a/tests/sentence_delim_props.rs b/tests/sentence_delim_props.rs index 059f0c9d..ab4fe2c3 100644 --- a/tests/sentence_delim_props.rs +++ b/tests/sentence_delim_props.rs @@ -156,10 +156,10 @@ fn plaintext_period_before_backticks_quote_is_span_safe() { fn plaintext_balanced_quote_period_backtick_is_span_safe() { let input = "\"\"`A`\"`a`.`A\""; assert!( - delimiters_balanced(&input), + delimiters_balanced(input), "seed must be balanced so the property would fire\n in={input:?}" ); - let out = format_plain(&input); + let out = format_plain(input); let again = format_plain(&out); assert_eq!( out, "\"\"`A`\"`a`.`A\"\n",