diff-cover 10.2.1__tar.gz → 10.3.0__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 (159) hide show
  1. {diff_cover-10.2.1 → diff_cover-10.3.0}/PKG-INFO +78 -1
  2. {diff_cover-10.2.1 → diff_cover-10.3.0}/README.rst +77 -0
  3. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/diff_cover_tool.py +19 -1
  4. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/report_generator.py +10 -1
  5. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/snippets.py +110 -14
  6. {diff_cover-10.2.1 → diff_cover-10.3.0}/pyproject.toml +1 -1
  7. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_diff_cover_tool.py +10 -0
  8. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_report_generator.py +33 -0
  9. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_snippets.py +128 -0
  10. {diff_cover-10.2.1 → diff_cover-10.3.0}/LICENSE +0 -0
  11. {diff_cover-10.2.1 → diff_cover-10.3.0}/NOTICE +0 -0
  12. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/__init__.py +0 -0
  13. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/command_runner.py +0 -0
  14. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/config_parser.py +0 -0
  15. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/diff_quality_tool.py +0 -0
  16. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/diff_reporter.py +0 -0
  17. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/git_diff.py +0 -0
  18. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/git_path.py +0 -0
  19. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/hook.py +0 -0
  20. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/hookspecs.py +0 -0
  21. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/console_coverage_report.txt +0 -0
  22. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/console_quality_report.txt +0 -0
  23. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/external_style.css +0 -0
  24. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/github_coverage_annotations.txt +0 -0
  25. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/html_coverage_report.html +0 -0
  26. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/html_quality_report.html +0 -0
  27. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/markdown_coverage_report.md +0 -0
  28. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/markdown_quality_report.md +0 -0
  29. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/snippet_content.html +0 -0
  30. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/snippet_content.md +0 -0
  31. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/snippet_content.txt +0 -0
  32. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/templates/snippet_style.html +0 -0
  33. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/util.py +0 -0
  34. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/violationsreporters/__init__.py +0 -0
  35. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/violationsreporters/base.py +0 -0
  36. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/violationsreporters/java_violations_reporter.py +0 -0
  37. {diff_cover-10.2.1 → diff_cover-10.3.0}/diff_cover/violationsreporters/violations_reporter.py +0 -0
  38. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/__init__.py +0 -0
  39. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/conftest.py +0 -0
  40. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/add_console_report.txt +0 -0
  41. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/add_html_report.html +0 -0
  42. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/add_json_report.json +0 -0
  43. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/add_json_report_float.json +0 -0
  44. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/add_markdown_report.md +0 -0
  45. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/changed_console_report.txt +0 -0
  46. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/changed_html_report.html +0 -0
  47. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/coverage.xml +0 -0
  48. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/coverage1.xml +0 -0
  49. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/coverage2.xml +0 -0
  50. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/coverage_missing_lines.xml +0 -0
  51. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/cpp_functions_console_report.txt +0 -0
  52. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/cpp_functions_coverage.lcov +0 -0
  53. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/delete_console_report.txt +0 -0
  54. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/delete_html_report.html +0 -0
  55. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/dotnet_coverage.xml +0 -0
  56. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/empty.txt +0 -0
  57. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/empty_pycodestyle_violations.txt +0 -0
  58. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/expand_console_report.txt +0 -0
  59. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/external_css_html_report.html +0 -0
  60. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/external_style.css +0 -0
  61. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_add.txt +0 -0
  62. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_changed.txt +0 -0
  63. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_code_dupe.txt +0 -0
  64. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_cpp.txt +0 -0
  65. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_cpp_functions.txt +0 -0
  66. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_delete.txt +0 -0
  67. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_dotnet.txt +0 -0
  68. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_external_css.txt +0 -0
  69. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_lua.txt +0 -0
  70. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_moved.txt +0 -0
  71. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_mult.txt +0 -0
  72. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_python.txt +0 -0
  73. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_subdir.txt +0 -0
  74. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_typescript.txt +0 -0
  75. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_typescript_branches.txt +0 -0
  76. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_unicode.txt +0 -0
  77. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_violations.txt +0 -0
  78. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/git_diff_violations_two_files.txt +0 -0
  79. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/hello.py +0 -0
  80. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/hi.py +0 -0
  81. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/html_report.html +0 -0
  82. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/html_report_empty.html +0 -0
  83. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/html_report_one_snippet.html +0 -0
  84. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/html_report_two_snippets.html +0 -0
  85. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/lcov.info +0 -0
  86. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/lua_console_report.txt +0 -0
  87. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/luacoverage.xml +0 -0
  88. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/markdown_report_one_snippet.md +0 -0
  89. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/markdown_report_two_snippets.md +0 -0
  90. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/moved_console_report.txt +0 -0
  91. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/moved_coverage.xml +0 -0
  92. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/moved_html_report.html +0 -0
  93. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/mult_inputs_console_report.txt +0 -0
  94. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/mult_inputs_html_report.html +0 -0
  95. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_report.txt +0 -0
  96. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_violations_report.html +0 -0
  97. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_violations_report.json +0 -0
  98. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_violations_report.md +0 -0
  99. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_violations_report.txt +0 -0
  100. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_violations_report_external_css.html +0 -0
  101. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pycodestyle_violations_report_float.json +0 -0
  102. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pyflakes_two_files.txt +0 -0
  103. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pyflakes_violations_report.html +0 -0
  104. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pyflakes_violations_report.txt +0 -0
  105. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylint_dupe.txt +0 -0
  106. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylint_dupe_violations_report.txt +0 -0
  107. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylint_report.txt +0 -0
  108. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylint_violations_console_report.txt +0 -0
  109. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylint_violations_report.html +0 -0
  110. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylint_violations_report.txt +0 -0
  111. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/pylintrc +0 -0
  112. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/real_cpp_console_report.txt +0 -0
  113. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/real_cpp_coverage.lcov +0 -0
  114. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/real_python_console_report.txt +0 -0
  115. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/real_python_coverage.lcov +0 -0
  116. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/real_typescript_console_report.txt +0 -0
  117. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/real_typescript_coverage.lcov +0 -0
  118. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/show_uncovered_lines_console.txt +0 -0
  119. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet.css +0 -0
  120. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_8859.py +0 -0
  121. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_arabic_output.html +0 -0
  122. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_default.html +0 -0
  123. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_invalid_violations.html +0 -0
  124. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_list.html +0 -0
  125. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_list.md +0 -0
  126. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_list2.md +0 -0
  127. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_list3.md +0 -0
  128. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_no_filename_ext.html +0 -0
  129. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_src.py +0 -0
  130. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_src2.cpp +0 -0
  131. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_src3.cpp +0 -0
  132. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_unicode.html +0 -0
  133. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_unicode.py +0 -0
  134. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/snippet_unicode_html_output.html +0 -0
  135. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/subdir_coverage_console_report.txt +0 -0
  136. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/subdir_coverage_html_report.html +0 -0
  137. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/test_src.txt +0 -0
  138. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/typescript_branches_console_report.txt +0 -0
  139. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/typescript_branches_coverage.lcov +0 -0
  140. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/unicode_console_report.txt +0 -0
  141. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/unicode_coverage.xml +0 -0
  142. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/unicode_html_report.html +0 -0
  143. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/unicode_test_src.txt +0 -0
  144. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/fixtures/violations_test_file.py +0 -0
  145. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/helpers.py +0 -0
  146. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/snippet_list_unicode.html +0 -0
  147. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_clover_violations_reporter/test.xml +0 -0
  148. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_clover_violations_reporter.py +0 -0
  149. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_config_parser.py +0 -0
  150. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_diff_cover_main.py +0 -0
  151. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_diff_quality_main.py +0 -0
  152. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_diff_reporter.py +0 -0
  153. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_git_diff.py +0 -0
  154. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_git_diff_file.py +0 -0
  155. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_git_path.py +0 -0
  156. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_integration.py +0 -0
  157. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_java_violations_reporter.py +0 -0
  158. {diff_cover-10.2.1 → diff_cover-10.3.0}/tests/test_util.py +0 -0
  159. {diff_cover-10.2.1 → diff_cover-10.3.0}/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.2.1
3
+ Version: 10.3.0
4
4
  Summary: Run coverage and linting reports on diffs
5
5
  License: Apache-2.0
6
6
  Author: See Contributors
@@ -320,6 +320,83 @@ It can be enabled by using the ``-q``/``--quiet`` flag:
320
320
 
321
321
  If enabled, the tool will only print errors and failures but no information or warning messages.
322
322
 
323
+ Showing Covered and Uncovered Lines
324
+ -----------------------------------
325
+ By default the console report only lists the percentage of covered lines.
326
+
327
+ ``diff-cover`` can also show the individual lines that lack coverage on the
328
+ console with ``--show-uncovered``:
329
+
330
+ .. code:: bash
331
+
332
+ diff-cover coverage.xml --show-uncovered
333
+
334
+ When generating an HTML report, ``--show-covered`` additionally highlights the
335
+ covered diff lines (in green), alongside the existing missing-line (red)
336
+ highlighting:
337
+
338
+ .. code:: bash
339
+
340
+ diff-cover coverage.xml --html-report report.html --show-covered
341
+
342
+ Source Roots
343
+ ------------
344
+ For ``jacoco`` coverage reports, ``diff-cover`` needs to know the source
345
+ directories in order to map coverage data back to source files. Provide one or
346
+ more directories with ``--src-roots`` (defaults to ``src/main/java`` and
347
+ ``src/test/java``):
348
+
349
+ .. code:: bash
350
+
351
+ diff-cover coverage.xml --src-roots src/main/java src/test/java
352
+
353
+ Report Root Path
354
+ ----------------
355
+ ``diff-quality`` resolves violation report paths relative to the current working
356
+ directory. If your quality tool was run from a different directory, use
357
+ ``--report-root-path`` to specify the root path that should be used when
358
+ generating the report:
359
+
360
+ .. code:: bash
361
+
362
+ diff-quality --violations=pycodestyle --report-root-path=path/to/root
363
+
364
+ External CSS File
365
+ -----------------
366
+ By default an HTML report embeds its styles inline. To write the CSS into a
367
+ separate file instead, use ``--external-css-file``:
368
+
369
+ .. code:: bash
370
+
371
+ diff-cover coverage.xml --html-report report.html --external-css-file report.css
372
+ diff-quality --violations=pycodestyle --html-report report.html --external-css-file report.css
373
+
374
+ Diff Range Notation
375
+ -------------------
376
+ When comparing branches, ``diff-cover`` and ``diff-quality`` use the ``...``
377
+ git diff range notation by default (changes on the current branch since it
378
+ diverged from the compare branch). To use the ``..`` notation instead (all
379
+ differences between the two branches), pass ``--diff-range-notation``:
380
+
381
+ .. code:: bash
382
+
383
+ diff-cover coverage.xml --diff-range-notation=..
384
+ diff-quality --violations=pycodestyle --diff-range-notation=..
385
+
386
+ See the `git diff documentation`_ for details on the two notations.
387
+
388
+ .. _git diff documentation: https://git-scm.com/docs/git-diff
389
+
390
+ Ignore Whitespace
391
+ -----------------
392
+ To ignore any and all whitespace changes when computing the diff, use
393
+ ``--ignore-whitespace``:
394
+
395
+ .. code:: bash
396
+
397
+ diff-cover coverage.xml --ignore-whitespace
398
+ diff-quality --violations=pycodestyle --ignore-whitespace
399
+
323
400
  Compatibility with multi-line statements
324
401
  ----------------------------------------
325
402
  ``diff-cover`` relies on the comparison of diff reports and coverage reports, and does not report
@@ -288,6 +288,83 @@ It can be enabled by using the ``-q``/``--quiet`` flag:
288
288
 
289
289
  If enabled, the tool will only print errors and failures but no information or warning messages.
290
290
 
291
+ Showing Covered and Uncovered Lines
292
+ -----------------------------------
293
+ By default the console report only lists the percentage of covered lines.
294
+
295
+ ``diff-cover`` can also show the individual lines that lack coverage on the
296
+ console with ``--show-uncovered``:
297
+
298
+ .. code:: bash
299
+
300
+ diff-cover coverage.xml --show-uncovered
301
+
302
+ When generating an HTML report, ``--show-covered`` additionally highlights the
303
+ covered diff lines (in green), alongside the existing missing-line (red)
304
+ highlighting:
305
+
306
+ .. code:: bash
307
+
308
+ diff-cover coverage.xml --html-report report.html --show-covered
309
+
310
+ Source Roots
311
+ ------------
312
+ For ``jacoco`` coverage reports, ``diff-cover`` needs to know the source
313
+ directories in order to map coverage data back to source files. Provide one or
314
+ more directories with ``--src-roots`` (defaults to ``src/main/java`` and
315
+ ``src/test/java``):
316
+
317
+ .. code:: bash
318
+
319
+ diff-cover coverage.xml --src-roots src/main/java src/test/java
320
+
321
+ Report Root Path
322
+ ----------------
323
+ ``diff-quality`` resolves violation report paths relative to the current working
324
+ directory. If your quality tool was run from a different directory, use
325
+ ``--report-root-path`` to specify the root path that should be used when
326
+ generating the report:
327
+
328
+ .. code:: bash
329
+
330
+ diff-quality --violations=pycodestyle --report-root-path=path/to/root
331
+
332
+ External CSS File
333
+ -----------------
334
+ By default an HTML report embeds its styles inline. To write the CSS into a
335
+ separate file instead, use ``--external-css-file``:
336
+
337
+ .. code:: bash
338
+
339
+ diff-cover coverage.xml --html-report report.html --external-css-file report.css
340
+ diff-quality --violations=pycodestyle --html-report report.html --external-css-file report.css
341
+
342
+ Diff Range Notation
343
+ -------------------
344
+ When comparing branches, ``diff-cover`` and ``diff-quality`` use the ``...``
345
+ git diff range notation by default (changes on the current branch since it
346
+ diverged from the compare branch). To use the ``..`` notation instead (all
347
+ differences between the two branches), pass ``--diff-range-notation``:
348
+
349
+ .. code:: bash
350
+
351
+ diff-cover coverage.xml --diff-range-notation=..
352
+ diff-quality --violations=pycodestyle --diff-range-notation=..
353
+
354
+ See the `git diff documentation`_ for details on the two notations.
355
+
356
+ .. _git diff documentation: https://git-scm.com/docs/git-diff
357
+
358
+ Ignore Whitespace
359
+ -----------------
360
+ To ignore any and all whitespace changes when computing the diff, use
361
+ ``--ignore-whitespace``:
362
+
363
+ .. code:: bash
364
+
365
+ diff-cover coverage.xml --ignore-whitespace
366
+ diff-quality --violations=pycodestyle --ignore-whitespace
367
+
291
368
  Compatibility with multi-line statements
292
369
  ----------------------------------------
293
370
  ``diff-cover`` relies on the comparison of diff reports and coverage reports, and does not report
@@ -45,6 +45,10 @@ DIFF_RANGE_NOTATION_HELP = (
45
45
  )
46
46
  QUIET_HELP = "Only print errors and failures"
47
47
  SHOW_UNCOVERED = "Show uncovered lines on the console"
48
+ SHOW_COVERED_HELP = (
49
+ "Also highlight covered diff lines (in green) in the HTML coverage report, "
50
+ "in addition to the existing missing-line (red) highlighting."
51
+ )
48
52
  EXPAND_COVERAGE_REPORT = (
49
53
  "Append missing lines in coverage reports based on the hits of the previous line."
50
54
  )
@@ -99,6 +103,13 @@ def parse_coverage_args(argv):
99
103
  "--show-uncovered", action="store_true", default=None, help=SHOW_UNCOVERED
100
104
  )
101
105
 
106
+ parser.add_argument(
107
+ "--show-covered",
108
+ action="store_true",
109
+ default=None,
110
+ help=SHOW_COVERED_HELP,
111
+ )
112
+
102
113
  parser.add_argument(
103
114
  "--expand-coverage-report",
104
115
  action="store_true",
@@ -193,6 +204,7 @@ def parse_coverage_args(argv):
193
204
 
194
205
  defaults = {
195
206
  "show_uncovered": False,
207
+ "show_covered": False,
196
208
  "compare_branch": "origin/main",
197
209
  "fail_under": 0,
198
210
  "ignore_staged": False,
@@ -223,6 +235,7 @@ def generate_coverage_report(
223
235
  src_roots=None,
224
236
  quiet=False,
225
237
  show_uncovered=False,
238
+ show_covered=False,
226
239
  expand_coverage_report=False,
227
240
  total_percent_float=False,
228
241
  ):
@@ -263,7 +276,11 @@ def generate_coverage_report(
263
276
  if css_url is not None:
264
277
  css_url = os.path.relpath(css_file, os.path.dirname(html_report))
265
278
  reporter = HtmlReportGenerator(
266
- coverage, diff, css_url=css_url, total_percent_float=total_percent_float
279
+ coverage,
280
+ diff,
281
+ css_url=css_url,
282
+ total_percent_float=total_percent_float,
283
+ show_covered=show_covered,
267
284
  )
268
285
  with open_file(html_report, "wb") as output_file:
269
286
  reporter.generate_report(output_file)
@@ -398,6 +415,7 @@ def main(argv=None, directory=None):
398
415
  src_roots=arg_dict["src_roots"],
399
416
  quiet=quiet,
400
417
  show_uncovered=arg_dict["show_uncovered"],
418
+ show_covered=arg_dict["show_covered"],
401
419
  expand_coverage_report=arg_dict["expand_coverage_report"],
402
420
  total_percent_float=arg_dict["total_percent_float"],
403
421
  )
@@ -295,6 +295,7 @@ class TemplateReportGenerator(BaseReportGenerator):
295
295
  diff_reporter,
296
296
  css_url=None,
297
297
  total_percent_float=False,
298
+ show_covered=False,
298
299
  ):
299
300
  super().__init__(
300
301
  violations_reporter,
@@ -302,6 +303,11 @@ class TemplateReportGenerator(BaseReportGenerator):
302
303
  total_percent_float=total_percent_float,
303
304
  )
304
305
  self.css_url = css_url
306
+ # When True, the rendered HTML snippet for each source file will
307
+ # additionally highlight covered diff lines (in green) on top of
308
+ # the existing violation (red) highlighting. Defaults to False to
309
+ # preserve historical behaviour.
310
+ self.show_covered = show_covered
305
311
 
306
312
  def generate_report(self, output_file):
307
313
  """
@@ -406,9 +412,12 @@ class TemplateReportGenerator(BaseReportGenerator):
406
412
  # If we cannot load the file, then fail gracefully
407
413
  formatted_snippets = {"html": [], "markdown": [], "terminal": []}
408
414
  if self.include_snippets:
415
+ covered_lines = stats["covered_lines"] if self.show_covered else None
409
416
  with contextlib.suppress(OSError):
410
417
  formatted_snippets = Snippet.load_formatted_snippets(
411
- src_path, stats["violation_lines"]
418
+ src_path,
419
+ stats["violation_lines"],
420
+ covered_lines=covered_lines,
412
421
  )
413
422
 
414
423
  stats.update(
@@ -4,6 +4,7 @@ in HTML reports.
4
4
  """
5
5
 
6
6
  import contextlib
7
+ import re
7
8
  from tokenize import open as openpy
8
9
 
9
10
  import chardet
@@ -23,7 +24,10 @@ class Snippet:
23
24
  """
24
25
 
25
26
  VIOLATION_COLOR = "#ffcccc"
27
+ COVERED_COLOR = "#ddffdd"
26
28
  DIV_CSS_CLASS = "snippet"
29
+ COVERED_LINE_CSS_CLASS = "diff-cover-covered-line"
30
+ LINESPANS_PREFIX = "diff-cover-src-line"
27
31
 
28
32
  # Number of extra lines to include before and after
29
33
  # each snippet to provide context.
@@ -50,6 +54,7 @@ class Snippet:
50
54
  last_line,
51
55
  violation_lines,
52
56
  lexer_name,
57
+ covered_lines=None,
53
58
  ):
54
59
  """
55
60
  Create a source code snippet.
@@ -75,6 +80,10 @@ class Snippet:
75
80
  programming language for this snippet.
76
81
  See https://pygments.org/docs/lexers/
77
82
 
83
+ `covered_lines` is an optional list of line numbers
84
+ to highlight as covered. When omitted (the default),
85
+ no covered-line highlighting is rendered.
86
+
78
87
  Raises a `ValueError` if `start_line` is less than 1
79
88
  """
80
89
  if start_line < 1:
@@ -86,6 +95,7 @@ class Snippet:
86
95
  self._last_line = last_line
87
96
  self._violation_lines = violation_lines
88
97
  self._lexer_name = lexer_name
98
+ self._covered_lines = covered_lines or []
89
99
 
90
100
  @classmethod
91
101
  def style_defs(cls):
@@ -95,21 +105,74 @@ class Snippet:
95
105
  """
96
106
  formatter = HtmlFormatter()
97
107
  formatter.style.highlight_color = cls.VIOLATION_COLOR
98
- return formatter.get_style_defs()
108
+ base_styles = formatter.get_style_defs()
109
+ # Append a rule for covered-line highlighting.
110
+ covered_style = (
111
+ f".{cls.COVERED_LINE_CSS_CLASS} "
112
+ f"{{ background-color: {cls.COVERED_COLOR}; }}"
113
+ )
114
+ return f"{base_styles}\n{covered_style}"
99
115
 
100
116
  def html(self):
101
117
  """
102
118
  Return an HTML representation of the snippet.
119
+
120
+ Violation lines are highlighted via Pygments' built-in
121
+ `hl_lines` mechanism. When the snippet was constructed with
122
+ `covered_lines`, those lines are additionally marked by adding
123
+ the covered-line CSS class to their per-line span, produced via
124
+ the `linespans` formatter option.
103
125
  """
104
- formatter = HtmlFormatter(
126
+ formatter_kwargs = dict(
105
127
  cssclass=self.DIV_CSS_CLASS,
106
128
  linenos=True,
107
129
  linenostart=self._start_line,
108
130
  hl_lines=self._shift_lines(self._violation_lines, self._start_line),
109
131
  lineanchors=self._src_filename,
110
132
  )
133
+ # Only enable per-line `<span id="...">` wrapping when we actually
134
+ # need it for covered-line highlighting. This keeps the rendered
135
+ # HTML byte-for-byte identical to the historical output when no
136
+ # covered lines are supplied.
137
+ if self._covered_lines:
138
+ formatter_kwargs["linespans"] = self.LINESPANS_PREFIX
111
139
 
112
- return pygments.format(self.src_tokens(), formatter)
140
+ rendered = pygments.format(self.src_tokens(), HtmlFormatter(**formatter_kwargs))
141
+
142
+ if self._covered_lines:
143
+ # NOTE: do NOT shift these line numbers. Unlike `hl_lines`
144
+ # (which is snippet-relative, 1-based), Pygments' `linespans`
145
+ # IDs are emitted using the same numbering as `linenostart`,
146
+ # i.e. the absolute file line number. So we match the raw
147
+ # values from `_covered_lines` directly.
148
+ rendered = self._mark_covered_lines(rendered, self._covered_lines)
149
+
150
+ return rendered
151
+
152
+ @classmethod
153
+ def _mark_covered_lines(cls, html, covered_file_lines):
154
+ """
155
+ Add the covered-line CSS class to per-line spans whose line
156
+ number is in `covered_file_lines`.
157
+
158
+ `covered_file_lines` are absolute (file-relative) line numbers,
159
+ matching the IDs that Pygments emits via `linespans` when
160
+ `linenostart` is set to the snippet's starting line number.
161
+ """
162
+ wanted = set(covered_file_lines)
163
+ if not wanted:
164
+ return html
165
+
166
+ prefix = cls.LINESPANS_PREFIX
167
+ css_class = cls.COVERED_LINE_CSS_CLASS
168
+
169
+ def _replace(match):
170
+ line_num = int(match.group(1))
171
+ if line_num in wanted:
172
+ return f'<span id="{prefix}-{line_num}" class="{css_class}">'
173
+ return match.group(0)
174
+
175
+ return re.sub(rf'<span id="{re.escape(prefix)}-(\d+)">', _replace, html)
113
176
 
114
177
  def markdown(self):
115
178
  """
@@ -174,24 +237,38 @@ class Snippet:
174
237
  return "".join([val for _, val in self._src_tokens])
175
238
 
176
239
  @classmethod
177
- def load_formatted_snippets(cls, src_path, violation_lines):
240
+ def load_formatted_snippets(cls, src_path, violation_lines, covered_lines=None):
178
241
  """
179
242
  Load snippets from the file at `src_path` and format
180
243
  them as HTML and as plain text.
181
244
  Returns a dictionary containing the two types of formatting
182
245
  results for code snippets.
183
246
 
247
+ If `covered_lines` is provided (non-empty), the HTML output will
248
+ additionally render snippets around those lines and highlight
249
+ them as covered. Markdown and terminal output is unchanged
250
+ regardless of `covered_lines`, to keep those formats stable.
251
+
184
252
  See `load_snippets()` for details.
185
253
  """
186
254
 
187
- # load once...
188
- snippet_list = cls.load_snippets(src_path, violation_lines)
255
+ # Snippets used for markdown/terminal output keep the historical
256
+ # behaviour: ranges only around violation lines, no covered-line
257
+ # information attached.
258
+ violation_only_snippets = cls.load_snippets(src_path, violation_lines)
259
+
260
+ if covered_lines:
261
+ # HTML rendering also visualises covered diff lines, so widen
262
+ # the snippet ranges to include them and pass the covered list
263
+ # through to the snippet for per-line highlighting.
264
+ html_snippets = cls.load_snippets(src_path, violation_lines, covered_lines)
265
+ else:
266
+ html_snippets = violation_only_snippets
189
267
 
190
- # ...render twice in different formats
191
268
  return {
192
- "html": [snippet.html() for snippet in snippet_list],
193
- "markdown": [snippet.markdown() for snippet in snippet_list],
194
- "terminal": [snippet.terminal() for snippet in snippet_list],
269
+ "html": [snippet.html() for snippet in html_snippets],
270
+ "markdown": [snippet.markdown() for snippet in violation_only_snippets],
271
+ "terminal": [snippet.terminal() for snippet in violation_only_snippets],
195
272
  }
196
273
 
197
274
  @classmethod
@@ -223,12 +300,17 @@ class Snippet:
223
300
  return contents
224
301
 
225
302
  @classmethod
226
- def load_snippets(cls, src_path, violation_lines):
303
+ def load_snippets(cls, src_path, violation_lines, covered_lines=None):
227
304
  """
228
305
  Load snippets from the file at `src_path` to show
229
306
  violations on lines in the list `violation_lines`
230
307
  (list of line numbers, starting at index 0).
231
308
 
309
+ If `covered_lines` is provided, those lines are also treated as
310
+ "interesting" when computing snippet ranges (so a fully-covered
311
+ file still yields snippets) and are stored on the resulting
312
+ `Snippet` instances for use during rendering.
313
+
232
314
  The file at `src_path` should be a text file (not binary).
233
315
 
234
316
  Returns a list of `Snippet` instances.
@@ -237,9 +319,15 @@ class Snippet:
237
319
  """
238
320
  contents = cls.load_contents(src_path)
239
321
 
240
- # Construct a list of snippet ranges
322
+ # Construct a list of snippet ranges. When `covered_lines` is
323
+ # provided, expand the set of "interesting" lines so we also
324
+ # render context around covered diff lines, not just violations.
241
325
  src_lines = contents.split("\n")
242
- snippet_ranges = cls._snippet_ranges(len(src_lines), violation_lines)
326
+ if covered_lines:
327
+ interesting_lines = sorted(set(violation_lines) | set(covered_lines))
328
+ else:
329
+ interesting_lines = violation_lines
330
+ snippet_ranges = cls._snippet_ranges(len(src_lines), interesting_lines)
243
331
 
244
332
  # Parse the source into tokens
245
333
  token_stream, lexer = cls._parse_src(contents, src_path)
@@ -248,7 +336,15 @@ class Snippet:
248
336
  token_groups = cls._group_tokens(token_stream, snippet_ranges)
249
337
 
250
338
  return [
251
- Snippet(tokens, src_path, start, end, violation_lines, lexer.name)
339
+ Snippet(
340
+ tokens,
341
+ src_path,
342
+ start,
343
+ end,
344
+ violation_lines,
345
+ lexer.name,
346
+ covered_lines=covered_lines,
347
+ )
252
348
  for (start, end), tokens in sorted(token_groups.items())
253
349
  ]
254
350
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "diff_cover"
3
- version = "10.2.1"
3
+ version = "10.3.0"
4
4
  description = "Run coverage and linting reports on diffs"
5
5
  authors = ["See Contributors"]
6
6
  homepage = "https://github.com/Bachmann1234/diff-cover"
@@ -37,6 +37,16 @@ def test_parse_with_no_report():
37
37
  assert not arg_dict["ignore_unstaged"]
38
38
 
39
39
 
40
+ def test_show_covered_defaults_false():
41
+ arg_dict = parse_coverage_args(["reports/coverage.xml"])
42
+ assert arg_dict["show_covered"] is False
43
+
44
+
45
+ def test_show_covered_flag():
46
+ arg_dict = parse_coverage_args(["reports/coverage.xml", "--show-covered"])
47
+ assert arg_dict["show_covered"] is True
48
+
49
+
40
50
  def test_parse_with_multiple_reports():
41
51
  argv = [
42
52
  "reports/coverage.xml",
@@ -498,6 +498,39 @@ class TestHtmlReportGenerator(BaseReportGeneratorTest):
498
498
  expected = load_fixture("html_report.html")
499
499
  self.assert_report(expected)
500
500
 
501
+ @pytest.mark.usefixtures("use_default_values")
502
+ def test_show_covered_passes_covered_lines_to_snippets(self, coverage, diff):
503
+ """
504
+ When `show_covered=True`, the html reporter should pass each
505
+ source file's covered lines into `Snippet.load_formatted_snippets`
506
+ so the snippet renderer can highlight them.
507
+ """
508
+ report = HtmlReportGenerator(coverage, diff, show_covered=True)
509
+ report.generate_report(BytesIO())
510
+
511
+ calls = self._load_formatted_snippets.call_args_list
512
+ assert calls, "Expected snippet loading to be invoked"
513
+ for call in calls:
514
+ assert call.kwargs.get(
515
+ "covered_lines"
516
+ ), "Expected covered_lines to be forwarded when show_covered=True"
517
+
518
+ @pytest.mark.usefixtures("use_default_values")
519
+ def test_show_covered_default_false_does_not_pass_covered_lines(
520
+ self, coverage, diff
521
+ ):
522
+ """
523
+ With the default `show_covered=False`, no covered_lines should be
524
+ passed to the snippet loader, preserving the historical behaviour.
525
+ """
526
+ report = HtmlReportGenerator(coverage, diff)
527
+ report.generate_report(BytesIO())
528
+
529
+ calls = self._load_formatted_snippets.call_args_list
530
+ assert calls, "Expected snippet loading to be invoked"
531
+ for call in calls:
532
+ assert call.kwargs.get("covered_lines") is None
533
+
501
534
  def test_empty_report(self):
502
535
  # Have the dependencies return an empty report
503
536
  # (this is the default)