From 062ad6d0e00b18942944c50857b949f2e19a07c1 Mon Sep 17 00:00:00 2001 From: Kyle King Date: Thu, 19 Dec 2024 06:21:27 -0500 Subject: [PATCH] add: `--no-validate` option to skip AST safety check (#496) * fix(#50): add --no-validate * refactor: use `dest=validate` Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> * refactor: finish renaming the stored value to validate * test: add patch to unit test * docs: add 'validate' to documentation * docs: re-generate help on Python 3.13.1 * test: add valid configuration test * fix: allow override * Apply suggestions from code review Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> * ci: apply formatting --------- Co-authored-by: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> --- README.md | 21 +++++++++++++-------- docs/users/configuration_file.md | 1 + src/mdformat/_cli.py | 23 +++++++++++++++++------ src/mdformat/_conf.py | 4 ++++ tests/test_cli.py | 13 +++++++++++++ tests/test_config_file.py | 20 ++++++++++++++++++++ 6 files changed, 68 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a562a55..4682e1a 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,10 @@ If a file is not properly formatted, the exit code will be non-zero. ```console foo@bar:~$ mdformat --help -usage: mdformat [-h] [--check] [--version] [--number] [--wrap {keep,no,INTEGER}] - [--end-of-line {lf,crlf,keep}] [--exclude PATTERN] - [--extensions EXTENSION] [--codeformatters LANGUAGE] +usage: mdformat [-h] [--check] [--no-validate] [--version] [--number] + [--wrap {keep,no,INTEGER}] [--end-of-line {lf,crlf,keep}] + [--exclude PATTERN] [--extensions EXTENSION] + [--codeformatters LANGUAGE] [paths ...] CommonMark compliant Markdown formatter @@ -106,19 +107,23 @@ positional arguments: options: -h, --help show this help message and exit --check do not apply changes to files + --no-validate do not validate that the rendered HTML is consistent --version show program's version number and exit --number apply consecutive numbering to ordered lists --wrap {keep,no,INTEGER} paragraph word wrap mode (default: keep) --end-of-line {lf,crlf,keep} output file line ending mode (default: lf) - --exclude PATTERN exclude files that match the Unix-style glob pattern (multiple allowed) + --exclude PATTERN exclude files that match the Unix-style glob pattern + (multiple allowed) --extensions EXTENSION - require and enable an extension plugin (multiple allowed) (use - `--no-extensions` to disable) (default: all enabled) + require and enable an extension plugin (multiple + allowed) (use `--no-extensions` to disable) (default: + all enabled) --codeformatters LANGUAGE - require and enable a code formatter plugin (multiple allowed) - (use `--no-codeformatters` to disable) (default: all enabled) + require and enable a code formatter plugin (multiple + allowed) (use `--no-codeformatters` to disable) + (default: all enabled) ``` The `--exclude` option is only available on Python 3.13+. diff --git a/docs/users/configuration_file.md b/docs/users/configuration_file.md index 31dfc3b..d21dd2f 100644 --- a/docs/users/configuration_file.md +++ b/docs/users/configuration_file.md @@ -20,6 +20,7 @@ Command line interface arguments take precedence over the configuration file. wrap = "keep" # options: {"keep", "no", INTEGER} number = false # options: {false, true} end_of_line = "lf" # options: {"lf", "crlf", "keep"} +validate = true # options: {false, true} # extensions = [ # options: a list of enabled extensions (default: all installed are enabled) # "gfm", # "toc", diff --git a/src/mdformat/_cli.py b/src/mdformat/_cli.py index 6f6744f..17c6632 100644 --- a/src/mdformat/_cli.py +++ b/src/mdformat/_cli.py @@ -142,12 +142,16 @@ def run(cli_args: Sequence[str]) -> int: # noqa: C901 getattr(plugin, "CHANGES_AST", False) for plugin in enabled_parserplugins.values() ) - if not changes_ast and not is_md_equal( - original_str, - formatted_str, - options=opts, - extensions=enabled_parserplugins, - codeformatters=enabled_codeformatters, + if ( + opts["validate"] + and not changes_ast + and not is_md_equal( + original_str, + formatted_str, + options=opts, + extensions=enabled_parserplugins, + codeformatters=enabled_codeformatters, + ) ): print_error( f'Could not format "{path_str}".', @@ -197,6 +201,13 @@ def make_arg_parser( parser.add_argument( "--check", action="store_true", help="do not apply changes to files" ) + parser.add_argument( + "--no-validate", + action="store_const", + const=False, + dest="validate", + help="do not validate that the rendered HTML is consistent", + ) version_str = f"mdformat {mdformat.__version__}" plugin_version_str = get_plugin_version_str( {**parser_extension_dists, **codeformatter_dists} diff --git a/src/mdformat/_conf.py b/src/mdformat/_conf.py index 132e99c..3642956 100644 --- a/src/mdformat/_conf.py +++ b/src/mdformat/_conf.py @@ -10,6 +10,7 @@ "wrap": "keep", "number": False, "end_of_line": "lf", + "validate": True, "exclude": [], "plugin": {}, "extensions": None, @@ -59,6 +60,9 @@ def _validate_values(opts: Mapping, conf_path: Path) -> None: # noqa: C901 if "end_of_line" in opts: if opts["end_of_line"] not in {"crlf", "lf", "keep"}: raise InvalidConfError(f"Invalid 'end_of_line' value in {conf_path}") + if "validate" in opts: + if not isinstance(opts["validate"], bool): + raise InvalidConfError(f"Invalid 'validate' value in {conf_path}") if "number" in opts: if not isinstance(opts["number"], bool): raise InvalidConfError(f"Invalid 'number' value in {conf_path}") diff --git a/tests/test_cli.py b/tests/test_cli.py index e4f3d3f..220b450 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -345,6 +345,19 @@ def test_eol__check_keep_crlf(tmp_path): assert run((str(file_path), "--check", "--end-of-line=keep")) == 1 +def test_cli_no_validate(tmp_path): + file_path = tmp_path / "test.md" + content = "1. ordered" + file_path.write_text(content) + + with patch("mdformat.renderer._context.get_list_marker_type", return_value="?"): + assert run((str(file_path),)) == 1 + assert file_path.read_text() == content + + assert run((str(file_path), "--no-validate")) == 0 + assert file_path.read_text() == "1? ordered\n" + + def test_get_plugin_info_str(): info = get_plugin_info_str( {"mdformat-tables": ("0.1.0", ["tables"])}, diff --git a/tests/test_config_file.py b/tests/test_config_file.py index 0c6ad02..4f5ec01 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -64,6 +64,7 @@ def test_invalid_toml(tmp_path, capsys): [ ("wrap", "wrap = -3"), ("end_of_line", "end_of_line = 'lol'"), + ("validate", "validate = 'off'"), ("number", "number = 0"), ("exclude", "exclude = '**'"), ("exclude", "exclude = ['1',3]"), @@ -149,3 +150,22 @@ def test_empty_exclude(tmp_path, capsys): assert run((str(tmp_path),)) == 0 assert file1_path.read_text() == FORMATTED_MARKDOWN + + +def test_conf_no_validate(tmp_path): + file_path = tmp_path / "file.md" + content = "1. ordered" + file_path.write_text(content) + + with mock.patch( + "mdformat.renderer._context.get_list_marker_type", + return_value="?", + ): + assert run_with_clear_cache((str(file_path),)) == 1 + assert file_path.read_text() == content + + config_path = tmp_path / ".mdformat.toml" + config_path.write_text("validate = false") + + assert run_with_clear_cache((str(file_path),)) == 0 + assert file_path.read_text() == "1? ordered\n"