diff-cover 10.4.0__tar.gz → 10.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. {diff_cover-10.4.0 → diff_cover-10.4.2}/PKG-INFO +16 -1
  2. {diff_cover-10.4.0 → diff_cover-10.4.2}/README.rst +15 -0
  3. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/diff_cover_tool.py +15 -2
  4. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/diff_quality_tool.py +29 -5
  5. diff_cover-10.4.2/diff_cover/hookspecs.py +16 -0
  6. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/snippets.py +20 -3
  7. {diff_cover-10.4.0 → diff_cover-10.4.2}/pyproject.toml +2 -2
  8. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_diff_cover_tool.py +28 -0
  9. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_diff_quality_main.py +46 -1
  10. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_snippets.py +39 -0
  11. diff_cover-10.4.0/diff_cover/hookspecs.py +0 -12
  12. {diff_cover-10.4.0 → diff_cover-10.4.2}/LICENSE +0 -0
  13. {diff_cover-10.4.0 → diff_cover-10.4.2}/NOTICE +0 -0
  14. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/__init__.py +0 -0
  15. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/command_runner.py +0 -0
  16. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/config_parser.py +0 -0
  17. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/diff_reporter.py +0 -0
  18. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/git_diff.py +0 -0
  19. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/git_path.py +0 -0
  20. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/hook.py +0 -0
  21. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/report_generator.py +0 -0
  22. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/console_coverage_report.txt +0 -0
  23. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/console_quality_report.txt +0 -0
  24. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/external_style.css +0 -0
  25. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/github_coverage_annotations.txt +0 -0
  26. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/html_coverage_report.html +0 -0
  27. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/html_quality_report.html +0 -0
  28. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/markdown_coverage_report.md +0 -0
  29. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/markdown_quality_report.md +0 -0
  30. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/snippet_content.html +0 -0
  31. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/snippet_content.md +0 -0
  32. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/snippet_content.txt +0 -0
  33. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/templates/snippet_style.html +0 -0
  34. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/util.py +0 -0
  35. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/violationsreporters/__init__.py +0 -0
  36. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/violationsreporters/base.py +0 -0
  37. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/violationsreporters/java_violations_reporter.py +0 -0
  38. {diff_cover-10.4.0 → diff_cover-10.4.2}/diff_cover/violationsreporters/violations_reporter.py +0 -0
  39. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/__init__.py +0 -0
  40. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/conftest.py +0 -0
  41. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/add_console_report.txt +0 -0
  42. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/add_html_report.html +0 -0
  43. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/add_json_report.json +0 -0
  44. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/add_json_report_float.json +0 -0
  45. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/add_markdown_report.md +0 -0
  46. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/changed_console_report.txt +0 -0
  47. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/changed_html_report.html +0 -0
  48. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/coverage.xml +0 -0
  49. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/coverage1.xml +0 -0
  50. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/coverage2.xml +0 -0
  51. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/coverage_missing_lines.xml +0 -0
  52. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/cpp_functions_console_report.txt +0 -0
  53. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/cpp_functions_coverage.lcov +0 -0
  54. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/delete_console_report.txt +0 -0
  55. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/delete_html_report.html +0 -0
  56. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/dotnet_coverage.xml +0 -0
  57. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/empty.txt +0 -0
  58. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/empty_pycodestyle_violations.txt +0 -0
  59. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/expand_console_report.txt +0 -0
  60. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/external_css_html_report.html +0 -0
  61. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/external_style.css +0 -0
  62. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_add.txt +0 -0
  63. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_changed.txt +0 -0
  64. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_code_dupe.txt +0 -0
  65. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_cpp.txt +0 -0
  66. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_cpp_functions.txt +0 -0
  67. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_delete.txt +0 -0
  68. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_dotnet.txt +0 -0
  69. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_external_css.txt +0 -0
  70. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_lua.txt +0 -0
  71. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_moved.txt +0 -0
  72. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_mult.txt +0 -0
  73. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_python.txt +0 -0
  74. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_subdir.txt +0 -0
  75. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_typescript.txt +0 -0
  76. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_typescript_branches.txt +0 -0
  77. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_unicode.txt +0 -0
  78. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_violations.txt +0 -0
  79. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/git_diff_violations_two_files.txt +0 -0
  80. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/hello.py +0 -0
  81. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/hi.py +0 -0
  82. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/html_report.html +0 -0
  83. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/html_report_empty.html +0 -0
  84. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/html_report_one_snippet.html +0 -0
  85. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/html_report_two_snippets.html +0 -0
  86. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/lcov.info +0 -0
  87. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/lua_console_report.txt +0 -0
  88. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/luacoverage.xml +0 -0
  89. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/markdown_report_one_snippet.md +0 -0
  90. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/markdown_report_two_snippets.md +0 -0
  91. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/moved_console_report.txt +0 -0
  92. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/moved_coverage.xml +0 -0
  93. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/moved_html_report.html +0 -0
  94. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/mult_inputs_console_report.txt +0 -0
  95. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/mult_inputs_html_report.html +0 -0
  96. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_report.txt +0 -0
  97. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_violations_report.html +0 -0
  98. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_violations_report.json +0 -0
  99. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_violations_report.md +0 -0
  100. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_violations_report.txt +0 -0
  101. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_violations_report_external_css.html +0 -0
  102. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pycodestyle_violations_report_float.json +0 -0
  103. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pyflakes_two_files.txt +0 -0
  104. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pyflakes_violations_report.html +0 -0
  105. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pyflakes_violations_report.txt +0 -0
  106. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylint_dupe.txt +0 -0
  107. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylint_dupe_violations_report.txt +0 -0
  108. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylint_report.txt +0 -0
  109. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylint_violations_console_report.txt +0 -0
  110. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylint_violations_report.html +0 -0
  111. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylint_violations_report.txt +0 -0
  112. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/pylintrc +0 -0
  113. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/real_cpp_console_report.txt +0 -0
  114. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/real_cpp_coverage.lcov +0 -0
  115. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/real_python_console_report.txt +0 -0
  116. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/real_python_coverage.lcov +0 -0
  117. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/real_typescript_console_report.txt +0 -0
  118. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/real_typescript_coverage.lcov +0 -0
  119. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/show_uncovered_lines_console.txt +0 -0
  120. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet.css +0 -0
  121. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_8859.py +0 -0
  122. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_arabic_output.html +0 -0
  123. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_default.html +0 -0
  124. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_invalid_violations.html +0 -0
  125. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_list.html +0 -0
  126. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_list.md +0 -0
  127. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_list2.md +0 -0
  128. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_list3.md +0 -0
  129. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_no_filename_ext.html +0 -0
  130. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_src.py +0 -0
  131. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_src2.cpp +0 -0
  132. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_src3.cpp +0 -0
  133. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_unicode.html +0 -0
  134. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_unicode.py +0 -0
  135. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/snippet_unicode_html_output.html +0 -0
  136. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/subdir_coverage_console_report.txt +0 -0
  137. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/subdir_coverage_html_report.html +0 -0
  138. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/test_src.txt +0 -0
  139. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/typescript_branches_console_report.txt +0 -0
  140. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/typescript_branches_coverage.lcov +0 -0
  141. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/unicode_console_report.txt +0 -0
  142. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/unicode_coverage.xml +0 -0
  143. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/unicode_html_report.html +0 -0
  144. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/unicode_test_src.txt +0 -0
  145. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/fixtures/violations_test_file.py +0 -0
  146. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/helpers.py +0 -0
  147. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/snippet_list_unicode.html +0 -0
  148. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_clover_violations_reporter/test.xml +0 -0
  149. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_clover_violations_reporter.py +0 -0
  150. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_config_parser.py +0 -0
  151. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_diff_cover_main.py +0 -0
  152. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_diff_reporter.py +0 -0
  153. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_git_diff.py +0 -0
  154. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_git_diff_file.py +0 -0
  155. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_git_path.py +0 -0
  156. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_integration.py +0 -0
  157. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_java_violations_reporter.py +0 -0
  158. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_report_generator.py +0 -0
  159. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_util.py +0 -0
  160. {diff_cover-10.4.0 → diff_cover-10.4.2}/tests/test_violations_reporter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: diff_cover
3
- Version: 10.4.0
3
+ Version: 10.4.2
4
4
  Summary: Run coverage and linting reports on diffs
5
5
  License: Apache-2.0
6
6
  Author: See Contributors
@@ -606,6 +606,21 @@ Important notes:
606
606
  * Marked with the ``@diff_cover_hookimpl`` decorator
607
607
  * Named ``diff_cover_report_quality``. (This distinguishes it from any other
608
608
  plugin types ``diff_cover`` may support.)
609
+ * The function may declare either, both, or neither of the hook's arguments:
610
+
611
+ * ``reports`` - the list of open file handles for the pre-generated reports
612
+ passed on the command line.
613
+ * ``options`` - the string given to ``--options``.
614
+
615
+ Only the arguments you declare are passed, so the zero-argument form above
616
+ keeps working. A plugin that wants the user's input would be written as:
617
+
618
+ .. code:: python
619
+
620
+ @diff_cover_hookimpl
621
+ def diff_cover_report_quality(reports, options):
622
+ return SQLFluffViolationReporter(reports=reports, options=options)
623
+
609
624
  * The function should return an object with the following properties and methods:
610
625
 
611
626
  * ``supported_extensions`` property with a list of supported file extensions
@@ -574,6 +574,21 @@ Important notes:
574
574
  * Marked with the ``@diff_cover_hookimpl`` decorator
575
575
  * Named ``diff_cover_report_quality``. (This distinguishes it from any other
576
576
  plugin types ``diff_cover`` may support.)
577
+ * The function may declare either, both, or neither of the hook's arguments:
578
+
579
+ * ``reports`` - the list of open file handles for the pre-generated reports
580
+ passed on the command line.
581
+ * ``options`` - the string given to ``--options``.
582
+
583
+ Only the arguments you declare are passed, so the zero-argument form above
584
+ keeps working. A plugin that wants the user's input would be written as:
585
+
586
+ .. code:: python
587
+
588
+ @diff_cover_hookimpl
589
+ def diff_cover_report_quality(reports, options):
590
+ return SQLFluffViolationReporter(reports=reports, options=options)
591
+
577
592
  * The function should return an object with the following properties and methods:
578
593
 
579
594
  * ``supported_extensions`` property with a list of supported file extensions
@@ -76,6 +76,16 @@ def format_type(value):
76
76
  return dict((item.split(":", 1) for item in value.split(",")) if value else {})
77
77
 
78
78
 
79
+ def normalize_format(config):
80
+ """
81
+ Config files provide ``format`` as a raw string, because ``type=`` is only
82
+ applied to values that come from the command line. Parse it the same way.
83
+ """
84
+ if isinstance(config.get("format"), str):
85
+ config["format"] = format_type(config["format"])
86
+ return config
87
+
88
+
79
89
  def parse_coverage_args(argv):
80
90
  """
81
91
  Parse command line arguments, returning a dict of
@@ -99,7 +109,7 @@ def parse_coverage_args(argv):
99
109
  parser.add_argument(
100
110
  "--format",
101
111
  type=format_type,
102
- default="",
112
+ default=None,
103
113
  help=FORMAT_HELP,
104
114
  )
105
115
 
@@ -217,6 +227,7 @@ def parse_coverage_args(argv):
217
227
  "show_uncovered": False,
218
228
  "show_covered": False,
219
229
  "compare_branch": "origin/main",
230
+ "format": {},
220
231
  "fail_under": 0,
221
232
  "ignore_staged": False,
222
233
  "ignore_unstaged": False,
@@ -230,7 +241,9 @@ def parse_coverage_args(argv):
230
241
  "total_percent_float": False,
231
242
  }
232
243
 
233
- return get_config(parser=parser, argv=argv, defaults=defaults, tool=Tool.DIFF_COVER)
244
+ return normalize_format(
245
+ get_config(parser=parser, argv=argv, defaults=defaults, tool=Tool.DIFF_COVER)
246
+ )
234
247
 
235
248
 
236
249
  def generate_coverage_report(
@@ -4,6 +4,7 @@ Implement the command-line tool interface for diff_quality.
4
4
 
5
5
  import argparse
6
6
  import contextlib
7
+ import inspect
7
8
  import io
8
9
  import logging
9
10
  import os
@@ -33,6 +34,7 @@ from diff_cover.diff_cover_tool import (
33
34
  TOTAL_PERCENT_FLOAT_HELP,
34
35
  format_type,
35
36
  handle_old_format,
37
+ normalize_format,
36
38
  )
37
39
  from diff_cover.diff_reporter import GitDiffReporter
38
40
  from diff_cover.git_diff import GitDiffTool
@@ -119,7 +121,7 @@ def parse_quality_args(argv):
119
121
  parser.add_argument(
120
122
  "--format",
121
123
  type=format_type,
122
- default="",
124
+ default=None,
123
125
  help=FORMAT_HELP,
124
126
  )
125
127
 
@@ -211,6 +213,7 @@ def parse_quality_args(argv):
211
213
  defaults = {
212
214
  "ignore_whitespace": False,
213
215
  "compare_branch": "origin/main",
216
+ "format": {},
214
217
  "diff_range_notation": "...",
215
218
  "input_reports": [],
216
219
  "fail_under": 0,
@@ -221,8 +224,8 @@ def parse_quality_args(argv):
221
224
  "total_percent_float": False,
222
225
  }
223
226
 
224
- return get_config(
225
- parser=parser, argv=argv, defaults=defaults, tool=Tool.DIFF_QUALITY
227
+ return normalize_format(
228
+ get_config(parser=parser, argv=argv, defaults=defaults, tool=Tool.DIFF_QUALITY)
226
229
  )
227
230
 
228
231
 
@@ -297,6 +300,27 @@ def generate_quality_report(
297
300
  return reporter.total_percent_covered()
298
301
 
299
302
 
303
+ def _call_reporter_factory(factory_fn, reports, options):
304
+ """
305
+ Call a plugin's ``diff_cover_report_quality`` implementation.
306
+
307
+ Plugins are free to declare only the arguments they need (including none
308
+ at all, as documented in the README), so pass only what the function
309
+ actually accepts.
310
+ """
311
+ available = {"reports": reports, "options": options}
312
+ try:
313
+ parameters = inspect.signature(factory_fn).parameters
314
+ except (TypeError, ValueError): # pragma: no cover - builtins/C callables
315
+ return factory_fn(**available)
316
+
317
+ if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters.values()):
318
+ return factory_fn(**available)
319
+
320
+ kwargs = {name: value for name, value in available.items() if name in parameters}
321
+ return factory_fn(**kwargs)
322
+
323
+
300
324
  def main(argv=None, directory=None):
301
325
  """
302
326
  Main entry point for the tool, script installed via pyproject.toml
@@ -366,8 +390,8 @@ def main(argv=None, directory=None):
366
390
 
367
391
  reporter = QualityReporter(driver, input_reports, user_options)
368
392
  elif reporter_factory_fn:
369
- reporter = reporter_factory_fn(
370
- reports=input_reports, options=user_options
393
+ reporter = _call_reporter_factory(
394
+ reporter_factory_fn, input_reports, user_options
371
395
  )
372
396
 
373
397
  percent_passing = generate_quality_report(
@@ -0,0 +1,16 @@
1
+ import pluggy
2
+
3
+ hookspec = pluggy.HookspecMarker("diff_cover")
4
+
5
+
6
+ @hookspec
7
+ def diff_cover_report_quality(reports, options): # pylint: disable=unused-argument
8
+ """
9
+ Return a 2-part tuple:
10
+ - Quality plugin name
11
+ - Object that implements the BaseViolationReporter protocol
12
+
13
+ ``reports`` is the list of open pre-generated report file handles and
14
+ ``options`` the user options string; both are passed by ``diff-quality``.
15
+ A plugin may declare either or neither argument.
16
+ """
@@ -17,6 +17,17 @@ from pygments.util import ClassNotFound
17
17
 
18
18
  from diff_cover.git_path import GitPathTool
19
19
 
20
+ # Below this, chardet is essentially guessing. Short files with only a
21
+ # handful of non-ascii bytes decode cleanly under every single byte
22
+ # encoding, so which one comes back is down to the detector's internals
23
+ # (chardet 7 answers MacRoman at 0.16 confidence for latin-1 text that
24
+ # chardet 5 called ISO-8859-1 at 0.73).
25
+ MIN_DETECTION_CONFIDENCE = 0.5
26
+
27
+ # What to try before an unconfident guess. cp1252 is the de facto default
28
+ # for undeclared western text and is a superset of latin-1 in practice.
29
+ FALLBACK_ENCODING = "cp1252"
30
+
20
31
 
21
32
  class Snippet:
22
33
  """
@@ -284,9 +295,15 @@ class Snippet:
284
295
  contents = src_file.read()
285
296
 
286
297
  if isinstance(contents, bytes):
287
- encoding = chardet.detect(contents).get("encoding", "utf-8")
288
- with contextlib.suppress(UnicodeDecodeError):
289
- contents = contents.decode(encoding)
298
+ detected = chardet.detect(contents)
299
+ candidates = [detected.get("encoding") or "utf-8"]
300
+ if (detected.get("confidence") or 0) < MIN_DETECTION_CONFIDENCE:
301
+ candidates.insert(0, FALLBACK_ENCODING)
302
+
303
+ for encoding in candidates:
304
+ with contextlib.suppress(UnicodeDecodeError, LookupError):
305
+ contents = contents.decode(encoding)
306
+ break
290
307
 
291
308
  if isinstance(contents, bytes):
292
309
  # We failed to decode the file.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "diff_cover"
3
- version = "10.4.0"
3
+ version = "10.4.2"
4
4
  description = "Run coverage and linting reports on diffs"
5
5
  authors = ["See Contributors"]
6
6
  homepage = "https://github.com/Bachmann1234/diff-cover"
@@ -61,7 +61,7 @@ pydocstyle = "^6.1.1"
61
61
  black = "^26.1.0"
62
62
  isort = ">=6.0.1,<9.0.0"
63
63
  doc8 = "2.0.0"
64
- ruff = ">=0.11.10,<0.16.0"
64
+ ruff = ">=0.11.10,<0.17.0"
65
65
 
66
66
  [tool.poetry.extras]
67
67
  toml = ["tomli"]
@@ -156,3 +156,31 @@ def test_parse_with_include():
156
156
 
157
157
  def test_parse_with_exclude():
158
158
  _test_parse_with_path_patterns("exclude")
159
+
160
+
161
+ def test_parse_format_from_config_file(tmp_path):
162
+ config_file = tmp_path / "diff_cover.toml"
163
+ config_file.write_text('[tool.diff_cover]\nformat = "html:report.html"\n')
164
+
165
+ arg_dict = parse_coverage_args(
166
+ ["reports/coverage.xml", "--config-file", str(config_file)]
167
+ )
168
+
169
+ assert arg_dict["format"] == {"html": "report.html"}
170
+
171
+
172
+ def test_parse_format_cli_overrides_config_file(tmp_path):
173
+ config_file = tmp_path / "diff_cover.toml"
174
+ config_file.write_text('[tool.diff_cover]\nformat = "html:report.html"\n')
175
+
176
+ arg_dict = parse_coverage_args(
177
+ [
178
+ "reports/coverage.xml",
179
+ "--config-file",
180
+ str(config_file),
181
+ "--format",
182
+ "json:report.json",
183
+ ]
184
+ )
185
+
186
+ assert arg_dict["format"] == {"json": "report.json"}
@@ -2,9 +2,15 @@
2
2
 
3
3
  """Test for diff_cover.diff_quality - main"""
4
4
 
5
+ import pluggy
5
6
  import pytest
6
7
 
7
- from diff_cover.diff_quality_tool import main, parse_quality_args
8
+ from diff_cover import hookspecs
9
+ from diff_cover.diff_quality_tool import (
10
+ _call_reporter_factory,
11
+ main,
12
+ parse_quality_args,
13
+ )
8
14
 
9
15
 
10
16
  def test_parse_with_html_report():
@@ -147,3 +153,42 @@ def _run_main(report, argv):
147
153
  quality_reporter = report.call_args[0][0]
148
154
  assert quality_reporter.driver.name == "pylint"
149
155
  assert quality_reporter.options == "--foobar"
156
+
157
+
158
+ def test_parse_format_from_config_file(tmp_path):
159
+ config_file = tmp_path / "diff_quality.toml"
160
+ config_file.write_text('[tool.diff_quality]\nformat = "html:report.html"\n')
161
+
162
+ arg_dict = parse_quality_args(
163
+ ["--violations", "pylint", "--config-file", str(config_file)]
164
+ )
165
+
166
+ assert arg_dict.get("format") == {"html": "report.html"}
167
+
168
+
169
+ def test_plugin_may_declare_hook_arguments():
170
+ """A plugin declaring reports/options must validate against the hookspec."""
171
+ hookimpl = pluggy.HookimplMarker("diff_cover")
172
+
173
+ class Plugin:
174
+ @hookimpl
175
+ def diff_cover_report_quality(self, reports, options):
176
+ return (reports, options)
177
+
178
+ plugin_manager = pluggy.PluginManager("diff_cover")
179
+ plugin_manager.add_hookspecs(hookspecs)
180
+ plugin_manager.register(Plugin(), name="myplugin")
181
+
182
+
183
+ @pytest.mark.parametrize(
184
+ "factory,expected",
185
+ [
186
+ (lambda: "none", "none"),
187
+ (lambda options: options, "--foobar"),
188
+ (lambda reports: reports, ["report"]),
189
+ (lambda reports, options: (reports, options), (["report"], "--foobar")),
190
+ (lambda **kwargs: kwargs, {"reports": ["report"], "options": "--foobar"}),
191
+ ],
192
+ )
193
+ def test_call_reporter_factory_passes_declared_arguments(factory, expected):
194
+ assert _call_reporter_factory(factory, ["report"], "--foobar") == expected
@@ -429,3 +429,42 @@ def test_latin_one_undeclared(tmp_path):
429
429
 
430
430
  contents = Snippet.load_contents(str(file))
431
431
  assert contents == "I am some latin 1 Â encoded text"
432
+
433
+
434
+ def test_unconfident_detection_falls_back(tmp_path, mocker):
435
+ # Which single byte encoding chardet picks for a short file varies by
436
+ # version (5 said ISO-8859-1, 6 WINDOWS-1252, 7 MacRoman) so pin the
437
+ # low confidence branch rather than depending on the installed one.
438
+ mocker.patch(
439
+ "diff_cover.snippets.chardet.detect",
440
+ return_value={"encoding": "MacRoman", "confidence": 0.16},
441
+ )
442
+ file = tmp_path / "tmp"
443
+ file.write_bytes("I am some latin 1 Â encoded text".encode("latin1"))
444
+
445
+ contents = Snippet.load_contents(str(file))
446
+ assert contents == "I am some latin 1 Â encoded text"
447
+
448
+
449
+ def test_confident_detection_is_trusted(tmp_path, mocker):
450
+ mocker.patch(
451
+ "diff_cover.snippets.chardet.detect",
452
+ return_value={"encoding": "MacRoman", "confidence": 0.99},
453
+ )
454
+ file = tmp_path / "tmp"
455
+ file.write_bytes("I am some latin 1 Â encoded text".encode("latin1"))
456
+
457
+ contents = Snippet.load_contents(str(file))
458
+ assert contents == "I am some latin 1 ¬ encoded text"
459
+
460
+
461
+ def test_detection_returns_no_encoding(tmp_path, mocker):
462
+ mocker.patch(
463
+ "diff_cover.snippets.chardet.detect",
464
+ return_value={"encoding": None, "confidence": None},
465
+ )
466
+ file = tmp_path / "tmp"
467
+ file.write_bytes("I am some latin 1 Â encoded text".encode("latin1"))
468
+
469
+ contents = Snippet.load_contents(str(file))
470
+ assert contents == "I am some latin 1 Â encoded text"
@@ -1,12 +0,0 @@
1
- import pluggy
2
-
3
- hookspec = pluggy.HookspecMarker("diff_cover")
4
-
5
-
6
- @hookspec
7
- def diff_cover_report_quality():
8
- """
9
- Return a 2-part tuple:
10
- - Quality plugin name
11
- - Object that implements the BaseViolationReporter protocol
12
- """
File without changes
File without changes