lintro 0.5.3__tar.gz → 0.6.1__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.

Potentially problematic release.


This version of lintro might be problematic. Click here for more details.

Files changed (196) hide show
  1. {lintro-0.5.3/lintro.egg-info → lintro-0.6.1}/PKG-INFO +9 -7
  2. {lintro-0.5.3 → lintro-0.6.1}/README.md +7 -5
  3. {lintro-0.5.3 → lintro-0.6.1}/docs/README.md +10 -9
  4. {lintro-0.5.3 → lintro-0.6.1}/docs/configuration.md +88 -0
  5. {lintro-0.5.3 → lintro-0.6.1}/docs/getting-started.md +7 -0
  6. {lintro-0.5.3 → lintro-0.6.1}/docs/github-integration.md +2 -0
  7. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/README.md +8 -0
  8. lintro-0.6.1/docs/tool-analysis/black-analysis.md +111 -0
  9. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/ruff-analysis.md +21 -0
  10. {lintro-0.5.3 → lintro-0.6.1}/lintro/__init__.py +1 -1
  11. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/actionlint_formatter.py +1 -1
  12. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/bandit_formatter.py +1 -1
  13. lintro-0.6.1/lintro/formatters/tools/black_formatter.py +48 -0
  14. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/darglint_formatter.py +3 -1
  15. lintro-0.6.1/lintro/parsers/black/black_issue.py +22 -0
  16. lintro-0.6.1/lintro/parsers/black/black_parser.py +90 -0
  17. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/darglint/darglint_parser.py +1 -1
  18. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/hadolint/hadolint_parser.py +2 -2
  19. lintro-0.6.1/lintro/parsers/ruff/ruff_parser.py +140 -0
  20. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/yamllint/yamllint_parser.py +2 -2
  21. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/__init__.py +2 -2
  22. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/core/tool_base.py +41 -11
  23. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_bandit.py +4 -2
  24. lintro-0.6.1/lintro/tools/implementations/tool_black.py +261 -0
  25. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_prettier.py +1 -1
  26. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_ruff.py +47 -25
  27. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/tool_enum.py +2 -0
  28. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/ascii_normalize_cli.py +3 -1
  29. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/config.py +16 -0
  30. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/console_logger.py +59 -11
  31. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/output_manager.py +2 -2
  32. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/tool_executor.py +214 -7
  33. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/tool_utils.py +12 -1
  34. {lintro-0.5.3 → lintro-0.6.1/lintro.egg-info}/PKG-INFO +9 -7
  35. {lintro-0.5.3 → lintro-0.6.1}/lintro.egg-info/SOURCES.txt +18 -0
  36. {lintro-0.5.3 → lintro-0.6.1}/lintro.egg-info/requires.txt +1 -1
  37. {lintro-0.5.3 → lintro-0.6.1}/pyproject.toml +14 -4
  38. lintro-0.6.1/test_samples/ruff_black_e501_wrappable.py +8 -0
  39. {lintro-0.5.3 → lintro-0.6.1}/tests/conftest.py +1 -1
  40. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/conftest.py +2 -2
  41. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_actionlint_integration.py +3 -1
  42. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_darglint_integration.py +18 -18
  43. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_hadolint_integration.py +38 -29
  44. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_prettier_integration.py +18 -16
  45. lintro-0.6.1/tests/integration/test_ruff_black_policy.py +82 -0
  46. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_ruff_integration.py +5 -5
  47. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_yamllint_integration.py +35 -24
  48. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/test_delete_previous_lintro_comments.py +3 -1
  49. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/test_ghcr_prune_untagged.py +41 -9
  50. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/test_script_environment.py +22 -13
  51. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/test_semantic_release_compute_next.py +16 -6
  52. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/test_shell_scripts.py +38 -28
  53. {lintro-0.5.3 → lintro-0.6.1}/tests/test_documentation.py +6 -6
  54. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_ascii_normalize.py +5 -1
  55. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_bandit_formatter_mapping.py +4 -1
  56. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_bandit_parsing.py +4 -4
  57. lintro-0.6.1/tests/unit/test_black_formatter.py +38 -0
  58. lintro-0.6.1/tests/unit/test_black_parser.py +29 -0
  59. lintro-0.6.1/tests/unit/test_black_tool.py +228 -0
  60. lintro-0.6.1/tests/unit/test_black_tool_more.py +41 -0
  61. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_cli_programmatic.py +10 -4
  62. lintro-0.6.1/tests/unit/test_compatibility_ruff_black.py +129 -0
  63. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_config_loader.py +24 -1
  64. lintro-0.6.1/tests/unit/test_config_loader_more.py +26 -0
  65. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_console_logger.py +14 -4
  66. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_console_logger_more.py +12 -2
  67. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_enums_and_normalizers.py +7 -7
  68. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_formatters_tables.py +6 -6
  69. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_output_manager_reports.py +4 -1
  70. lintro-0.6.1/tests/unit/test_ruff_parser_additional.py +30 -0
  71. lintro-0.6.1/tests/unit/test_ruff_parser_more.py +57 -0
  72. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_tool_executor.py +11 -7
  73. lintro-0.6.1/tests/unit/test_tool_executor_more.py +389 -0
  74. lintro-0.6.1/tests/unit/test_tool_executor_post_checks.py +210 -0
  75. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_tool_manager.py +4 -5
  76. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_tool_utils.py +5 -1
  77. lintro-0.6.1/tests/unit/test_tool_utils_fallbacks.py +30 -0
  78. lintro-0.5.3/lintro/parsers/ruff/ruff_parser.py +0 -89
  79. {lintro-0.5.3 → lintro-0.6.1}/LICENSE +0 -0
  80. {lintro-0.5.3 → lintro-0.6.1}/MANIFEST.in +0 -0
  81. {lintro-0.5.3 → lintro-0.6.1}/assets/images/coverage-badge.svg +0 -0
  82. {lintro-0.5.3 → lintro-0.6.1}/assets/images/lintro.png +0 -0
  83. {lintro-0.5.3 → lintro-0.6.1}/docs/contributing.md +0 -0
  84. {lintro-0.5.3 → lintro-0.6.1}/docs/coverage-setup.md +0 -0
  85. {lintro-0.5.3 → lintro-0.6.1}/docs/docker.md +0 -0
  86. {lintro-0.5.3 → lintro-0.6.1}/docs/lintro-self-use.md +0 -0
  87. {lintro-0.5.3 → lintro-0.6.1}/docs/style-guide.md +0 -0
  88. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/actionlint-analysis.md +0 -0
  89. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/bandit-analysis.md +0 -0
  90. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/darglint-analysis.md +0 -0
  91. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/hadolint-analysis.md +0 -0
  92. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/prettier-analysis.md +0 -0
  93. {lintro-0.5.3 → lintro-0.6.1}/docs/tool-analysis/yamllint-analysis.md +0 -0
  94. {lintro-0.5.3 → lintro-0.6.1}/lintro/__main__.py +0 -0
  95. {lintro-0.5.3 → lintro-0.6.1}/lintro/ascii-art/fail.txt +0 -0
  96. {lintro-0.5.3 → lintro-0.6.1}/lintro/ascii-art/success.txt +0 -0
  97. {lintro-0.5.3 → lintro-0.6.1}/lintro/cli.py +0 -0
  98. {lintro-0.5.3 → lintro-0.6.1}/lintro/cli_utils/__init__.py +0 -0
  99. {lintro-0.5.3 → lintro-0.6.1}/lintro/cli_utils/commands/__init__.py +0 -0
  100. {lintro-0.5.3 → lintro-0.6.1}/lintro/cli_utils/commands/check.py +0 -0
  101. {lintro-0.5.3 → lintro-0.6.1}/lintro/cli_utils/commands/format.py +0 -0
  102. {lintro-0.5.3 → lintro-0.6.1}/lintro/cli_utils/commands/list_tools.py +0 -0
  103. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/__init__.py +0 -0
  104. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/action.py +0 -0
  105. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/darglint_strictness.py +0 -0
  106. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/group_by.py +0 -0
  107. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/hadolint_enums.py +0 -0
  108. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/output_format.py +0 -0
  109. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/tool_name.py +0 -0
  110. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/tool_type.py +0 -0
  111. {lintro-0.5.3 → lintro-0.6.1}/lintro/enums/yamllint_format.py +0 -0
  112. {lintro-0.5.3 → lintro-0.6.1}/lintro/exceptions/__init__.py +0 -0
  113. {lintro-0.5.3 → lintro-0.6.1}/lintro/exceptions/errors.py +0 -0
  114. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/__init__.py +0 -0
  115. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/core/__init__.py +0 -0
  116. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/core/output_style.py +0 -0
  117. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/core/table_descriptor.py +0 -0
  118. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/__init__.py +0 -0
  119. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/csv.py +0 -0
  120. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/grid.py +0 -0
  121. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/html.py +0 -0
  122. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/json.py +0 -0
  123. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/markdown.py +0 -0
  124. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/styles/plain.py +0 -0
  125. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/__init__.py +0 -0
  126. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/hadolint_formatter.py +0 -0
  127. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/prettier_formatter.py +0 -0
  128. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/ruff_formatter.py +0 -0
  129. {lintro-0.5.3 → lintro-0.6.1}/lintro/formatters/tools/yamllint_formatter.py +0 -0
  130. {lintro-0.5.3 → lintro-0.6.1}/lintro/models/__init__.py +0 -0
  131. {lintro-0.5.3 → lintro-0.6.1}/lintro/models/core/__init__.py +0 -0
  132. {lintro-0.5.3 → lintro-0.6.1}/lintro/models/core/tool.py +0 -0
  133. {lintro-0.5.3 → lintro-0.6.1}/lintro/models/core/tool_config.py +0 -0
  134. {lintro-0.5.3 → lintro-0.6.1}/lintro/models/core/tool_result.py +0 -0
  135. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/__init__.py +0 -0
  136. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/actionlint/__init__.py +0 -0
  137. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/actionlint/actionlint_issue.py +0 -0
  138. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/actionlint/actionlint_parser.py +0 -0
  139. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/darglint/__init__.py +0 -0
  140. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/darglint/darglint_issue.py +0 -0
  141. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/hadolint/__init__.py +0 -0
  142. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/hadolint/hadolint_issue.py +0 -0
  143. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/prettier/__init__.py +0 -0
  144. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/prettier/prettier_issue.py +0 -0
  145. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/prettier/prettier_parser.py +0 -0
  146. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/ruff/__init__.py +0 -0
  147. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/ruff/ruff_issue.py +0 -0
  148. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/yamllint/__init__.py +0 -0
  149. {lintro-0.5.3 → lintro-0.6.1}/lintro/parsers/yamllint/yamllint_issue.py +0 -0
  150. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/core/__init__.py +0 -0
  151. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/core/tool_manager.py +0 -0
  152. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/__init__.py +0 -0
  153. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_actionlint.py +0 -0
  154. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_darglint.py +0 -0
  155. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_hadolint.py +0 -0
  156. {lintro-0.5.3 → lintro-0.6.1}/lintro/tools/implementations/tool_yamllint.py +0 -0
  157. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/__init__.py +0 -0
  158. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/formatting.py +0 -0
  159. {lintro-0.5.3 → lintro-0.6.1}/lintro/utils/path_utils.py +0 -0
  160. {lintro-0.5.3 → lintro-0.6.1}/lintro.egg-info/dependency_links.txt +0 -0
  161. {lintro-0.5.3 → lintro-0.6.1}/lintro.egg-info/entry_points.txt +0 -0
  162. {lintro-0.5.3 → lintro-0.6.1}/lintro.egg-info/top_level.txt +0 -0
  163. {lintro-0.5.3 → lintro-0.6.1}/setup.cfg +0 -0
  164. {lintro-0.5.3 → lintro-0.6.1}/test_samples/Dockerfile.violations +0 -0
  165. {lintro-0.5.3 → lintro-0.6.1}/test_samples/actionlint_violations.yml +0 -0
  166. {lintro-0.5.3 → lintro-0.6.1}/test_samples/bandit_violations.py +0 -0
  167. {lintro-0.5.3 → lintro-0.6.1}/test_samples/darglint_violations.py +0 -0
  168. {lintro-0.5.3 → lintro-0.6.1}/test_samples/prettier_violations.js +0 -0
  169. {lintro-0.5.3 → lintro-0.6.1}/test_samples/ruff_clean.py +0 -0
  170. {lintro-0.5.3 → lintro-0.6.1}/test_samples/ruff_violations.py +0 -0
  171. {lintro-0.5.3 → lintro-0.6.1}/test_samples/yaml_violations.yml +0 -0
  172. {lintro-0.5.3 → lintro-0.6.1}/tests/__init__.py +0 -0
  173. {lintro-0.5.3 → lintro-0.6.1}/tests/cli/__init__.py +0 -0
  174. {lintro-0.5.3 → lintro-0.6.1}/tests/cli/conftest.py +0 -0
  175. {lintro-0.5.3 → lintro-0.6.1}/tests/cli/test_cli.py +0 -0
  176. {lintro-0.5.3 → lintro-0.6.1}/tests/formatters/__init__.py +0 -0
  177. {lintro-0.5.3 → lintro-0.6.1}/tests/formatters/conftest.py +0 -0
  178. {lintro-0.5.3 → lintro-0.6.1}/tests/formatters/test_formatters.py +0 -0
  179. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/__init__.py +0 -0
  180. {lintro-0.5.3 → lintro-0.6.1}/tests/integration/test_bandit_integration.py +0 -0
  181. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/__init__.py +0 -0
  182. {lintro-0.5.3 → lintro-0.6.1}/tests/scripts/test_extract_version.py +0 -0
  183. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/__init__.py +0 -0
  184. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_bandit_command_building.py +0 -0
  185. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_bandit_config_hydration.py +0 -0
  186. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_cli_commands.py +0 -0
  187. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_cli_commands_more.py +0 -0
  188. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_exceptions.py +0 -0
  189. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_parsers_actionlint.py +0 -0
  190. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_tool_executor_fmt_exclusion.py +0 -0
  191. {lintro-0.5.3 → lintro-0.6.1}/tests/unit/test_tool_utils_more.py +0 -0
  192. {lintro-0.5.3 → lintro-0.6.1}/tests/utils/__init__.py +0 -0
  193. {lintro-0.5.3 → lintro-0.6.1}/tests/utils/conftest.py +0 -0
  194. {lintro-0.5.3 → lintro-0.6.1}/tests/utils/test_formatting.py +0 -0
  195. {lintro-0.5.3 → lintro-0.6.1}/tests/utils/test_output_manager.py +0 -0
  196. {lintro-0.5.3 → lintro-0.6.1}/tests/utils/test_path_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lintro
3
- Version: 0.5.3
3
+ Version: 0.6.1
4
4
  Summary: A unified CLI tool for code formatting, linting, and quality assurance
5
5
  Author-email: TurboCoder13 <turbocoder13@gmail.com>
6
6
  License: MIT License
@@ -58,7 +58,7 @@ Requires-Dist: allure-pytest==2.15.0; extra == "dev"
58
58
  Requires-Dist: ruff; extra == "dev"
59
59
  Requires-Dist: mypy; extra == "dev"
60
60
  Requires-Dist: coverage-badge==1.1.2; extra == "dev"
61
- Requires-Dist: python-semantic-release==10.3.1; extra == "dev"
61
+ Requires-Dist: python-semantic-release==10.3.2; extra == "dev"
62
62
  Requires-Dist: assertpy==1.1; extra == "dev"
63
63
  Requires-Dist: httpx==0.28.1; extra == "dev"
64
64
  Provides-Extra: test
@@ -121,6 +121,7 @@ Lintro is a unified command-line interface that brings together multiple code qu
121
121
  | [![Darglint](https://img.shields.io/badge/Darglint-docstrings-3776AB?logo=python&logoColor=white)](https://github.com/terrencepreilly/darglint) | 🐍 Python | - |
122
122
  | [![Hadolint](https://img.shields.io/badge/Hadolint-lint-2496ED?logo=docker&logoColor=white)](https://github.com/hadolint/hadolint) | 🐳 Dockerfile | - |
123
123
  | [![Prettier](https://img.shields.io/badge/Prettier-format-1a2b34?logo=prettier&logoColor=white)](https://prettier.io/) | 🟨 JS/TS · 🧾 JSON | ✅ |
124
+ | [![Black](https://img.shields.io/badge/Black-format-000000?logo=python&logoColor=white)](https://github.com/psf/black) | 🐍 Python | ✅ |
124
125
  | [![Ruff](https://img.shields.io/badge/Ruff-lint%2Bformat-000?logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff) | 🐍 Python | ✅ |
125
126
  | [![Yamllint](https://img.shields.io/badge/Yamllint-lint-cb171e?logo=yaml&logoColor=white)](https://github.com/adrienverge/yamllint) | 🧾 YAML | - |
126
127
 
@@ -216,11 +217,12 @@ lintro check --exclude "migrations,node_modules,dist"
216
217
  # Tool-specific options use key=value (lists with |)
217
218
  lintro check --tool-options "ruff:line_length=88,prettier:print_width=80"
218
219
 
219
- # Ruff fix configuration (fmt):
220
- # By default, fmt applies both lint fixes and formatting for Ruff.
221
- # Disable either stage as needed:
222
- lintro format --tool-options ruff:lint_fix=False # format only
223
- lintro format --tool-options ruff:format=False # lint fixes only
220
+ # Ruff + Black cooperation:
221
+ # Lintro prefers Ruff for linting and Black for formatting.
222
+ # When Black is configured as a post-check, Lintro disables Ruff formatting by
223
+ # default to avoid double-formatting. Override if needed:
224
+ lintro format --tool-options ruff:format=True # force Ruff to format
225
+ lintro check --tool-options ruff:format_check=True # force Ruff format check
224
226
  ```
225
227
 
226
228
  ### CI/CD Integration
@@ -47,6 +47,7 @@ Lintro is a unified command-line interface that brings together multiple code qu
47
47
  | [![Darglint](https://img.shields.io/badge/Darglint-docstrings-3776AB?logo=python&logoColor=white)](https://github.com/terrencepreilly/darglint) | 🐍 Python | - |
48
48
  | [![Hadolint](https://img.shields.io/badge/Hadolint-lint-2496ED?logo=docker&logoColor=white)](https://github.com/hadolint/hadolint) | 🐳 Dockerfile | - |
49
49
  | [![Prettier](https://img.shields.io/badge/Prettier-format-1a2b34?logo=prettier&logoColor=white)](https://prettier.io/) | 🟨 JS/TS · 🧾 JSON | ✅ |
50
+ | [![Black](https://img.shields.io/badge/Black-format-000000?logo=python&logoColor=white)](https://github.com/psf/black) | 🐍 Python | ✅ |
50
51
  | [![Ruff](https://img.shields.io/badge/Ruff-lint%2Bformat-000?logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff) | 🐍 Python | ✅ |
51
52
  | [![Yamllint](https://img.shields.io/badge/Yamllint-lint-cb171e?logo=yaml&logoColor=white)](https://github.com/adrienverge/yamllint) | 🧾 YAML | - |
52
53
 
@@ -142,11 +143,12 @@ lintro check --exclude "migrations,node_modules,dist"
142
143
  # Tool-specific options use key=value (lists with |)
143
144
  lintro check --tool-options "ruff:line_length=88,prettier:print_width=80"
144
145
 
145
- # Ruff fix configuration (fmt):
146
- # By default, fmt applies both lint fixes and formatting for Ruff.
147
- # Disable either stage as needed:
148
- lintro format --tool-options ruff:lint_fix=False # format only
149
- lintro format --tool-options ruff:format=False # lint fixes only
146
+ # Ruff + Black cooperation:
147
+ # Lintro prefers Ruff for linting and Black for formatting.
148
+ # When Black is configured as a post-check, Lintro disables Ruff formatting by
149
+ # default to avoid double-formatting. Override if needed:
150
+ lintro format --tool-options ruff:format=True # force Ruff to format
151
+ lintro check --tool-options ruff:format_check=True # force Ruff format check
150
152
  ```
151
153
 
152
154
  ### CI/CD Integration
@@ -105,15 +105,16 @@ lintro format
105
105
 
106
106
  ## 🛠️ Supported Tools
107
107
 
108
- | Tool | Language/Format | Purpose | Documentation |
109
- | -------------- | ---------------- | -------------------- | ------------------------------------------------------- |
110
- | **Ruff** | Python | Linting & Formatting | [Config Guide](configuration.md#ruff-configuration) |
111
- | **Darglint** | Python | Docstring Validation | [Analysis](tool-analysis/darglint-analysis.md) |
112
- | **Bandit** | Python | Security Linting | [Analysis](tool-analysis/bandit-analysis.md) |
113
- | **Prettier** | JS/TS/JSON/CSS | Code Formatting | [Analysis](tool-analysis/prettier-analysis.md) |
114
- | **Yamllint** | YAML | Syntax & Style | [Config Guide](configuration.md#yamllint-configuration) |
115
- | **Actionlint** | GitHub Workflows | Workflow Linting | [Analysis](tool-analysis/actionlint-analysis.md) |
116
- | **Hadolint** | Dockerfile | Best Practices | [Config Guide](configuration.md#hadolint-configuration) |
108
+ | Tool | Language/Format | Purpose | Documentation |
109
+ | -------------- | ---------------- | ----------------------- | ---------------------------------------------------------- |
110
+ | **Ruff** | Python | Linting & Formatting | [Config Guide](configuration.md#ruff-configuration) |
111
+ | **Black** | Python | Formatting (Post-check) | [Config Guide](configuration.md#post-checks-configuration) |
112
+ | **Darglint** | Python | Docstring Validation | [Analysis](tool-analysis/darglint-analysis.md) |
113
+ | **Bandit** | Python | Security Linting | [Analysis](tool-analysis/bandit-analysis.md) |
114
+ | **Prettier** | JS/TS/JSON/CSS | Code Formatting | [Analysis](tool-analysis/prettier-analysis.md) |
115
+ | **Yamllint** | YAML | Syntax & Style | [Config Guide](configuration.md#yamllint-configuration) |
116
+ | **Actionlint** | GitHub Workflows | Workflow Linting | [Analysis](tool-analysis/actionlint-analysis.md) |
117
+ | **Hadolint** | Dockerfile | Best Practices | [Config Guide](configuration.md#hadolint-configuration) |
117
118
 
118
119
  ## 📋 Command Reference
119
120
 
@@ -69,6 +69,94 @@ format = true # run `ruff format` during fmt (default true)
69
69
  lint_fix = true # run `ruff check --fix` during fmt (default true)
70
70
  ```
71
71
 
72
+ ### Post-checks Configuration
73
+
74
+ Black is integrated as a post-check tool by default. Post-checks run after the
75
+ main tools complete and can be configured to enforce failure if issues are
76
+ found. This avoids double-formatting with Ruff and keeps formatting decisions
77
+ explicit.
78
+
79
+ ```toml
80
+ [tool.lintro.post_checks]
81
+ enabled = true
82
+ tools = ["black"] # Black runs after core tools
83
+ enforce_failure = true # Fail the run if Black finds issues in check mode
84
+ ```
85
+
86
+ Notes:
87
+
88
+ - With post-checks enabled for Black, Ruff’s `format`/`format_check` stages can
89
+ be disabled or overridden via CLI when desired.
90
+ - In `lintro check`, Black runs with `--check` and contributes to failure when
91
+ `enforce_failure` is true. In `lintro format`, Black formats files in the
92
+ post-check phase.
93
+
94
+ #### Black Options via `--tool-options`
95
+
96
+ You can override Black behavior on the CLI. Supported options include
97
+ `line_length`, `target_version`, `fast`, `preview`, and `diff`.
98
+
99
+ ```bash
100
+ # Increase line length and target a specific Python version
101
+ lintro check --tool-options "black:line_length=100,black:target_version=py313"
102
+
103
+ # Enable fast and preview modes
104
+ lintro format --tool-options "black:fast=True,black:preview=True"
105
+
106
+ # Show diffs during formatting (in addition to applying changes)
107
+ lintro format --tool-options "black:diff=True"
108
+ ```
109
+
110
+ These options can also be set in `pyproject.toml` under `[tool.lintro.black]`:
111
+
112
+ ```toml
113
+ [tool.lintro.black]
114
+ line_length = 100
115
+ target_version = "py313"
116
+ fast = false
117
+ preview = false
118
+ diff = false
119
+ ```
120
+
121
+ ### Ruff vs Black Policy (Python)
122
+
123
+ Lintro enforces Ruff-first linting and Black-first formatting when Black is
124
+ configured as a post-check.
125
+
126
+ - Ruff: primary linter (keep strict rules like `COM812` trailing commas and
127
+ `E501` line length enabled for checks)
128
+ - Black: primary formatter (applies formatting during post-checks; performs
129
+ safe line breaking where Ruff’s auto-format may be limited)
130
+
131
+ Runtime behavior with Black as post-check:
132
+
133
+ - lintro format
134
+ - Ruff fixes lint issues only (Ruff `format=False`) unless explicitly
135
+ overridden
136
+ - Black performs formatting in the post-check phase
137
+
138
+ - lintro check
139
+ - Ruff runs lint checks (Ruff `format_check=False`) unless explicitly
140
+ overridden
141
+ - Black runs `--check` as a post-check to enforce formatting
142
+
143
+ Overrides when needed:
144
+
145
+ ```bash
146
+ # Force Ruff to format during fmt
147
+ lintro format --tool-options ruff:format=True
148
+
149
+ # Force Ruff to include format check during check
150
+ lintro check --tool-options ruff:format_check=True
151
+ ```
152
+
153
+ Rationale:
154
+
155
+ - Avoids double-formatting churn (Ruff format followed by Black format) while
156
+ preserving Ruff’s stricter lint rules (e.g., `COM812`, `E501`).
157
+ - Black’s safe wrapping is preferred for long lines; Ruff continues to enforce
158
+ lint limits during checks.
159
+
72
160
  ### Python Tools
73
161
 
74
162
  #### Ruff Configuration
@@ -120,11 +120,18 @@ lintro check src/ tests/ --tools ruff,darglint
120
120
 
121
121
  # Format Python code
122
122
  lintro format src/ --tools ruff
123
+
124
+ # Run Black as a post-check (configured via pyproject)
125
+ lintro check src/
126
+
127
+ # Override Black on the fly
128
+ lintro check src/ --tool-options "black:line_length=100,black:target_version=py313"
123
129
  ```
124
130
 
125
131
  **Tools:**
126
132
 
127
133
  - **Ruff** - Fast Python linter and formatter
134
+ - **Black** - Python formatter (runs as a post-check by default)
128
135
  - **Darglint** - Docstring validation
129
136
 
130
137
  ### JavaScript/TypeScript Projects
@@ -23,6 +23,7 @@ The repository includes pre-configured GitHub Actions workflows. To activate the
23
23
  - 📊 **Detailed reporting** in GitHub Actions summaries
24
24
  - 🚀 **Multi-tool analysis:**
25
25
  - Python: Ruff + Darglint
26
+ - Python formatting (post-check): Black
26
27
  - YAML: Yamllint
27
28
  - JSON: Prettier
28
29
  - Docker: Hadolint
@@ -248,6 +249,7 @@ jobs:
248
249
 
249
250
  - name: Run Lintro
250
251
  run: |
252
+ # Run core tools, then post-checks (Black) per pyproject config
251
253
  uv run lintro check --output-format grid --output lintro-results.txt
252
254
  cat lintro-results.txt
253
255
 
@@ -60,6 +60,14 @@ This directory contains comprehensive analyses comparing Lintro's wrapper implem
60
60
  - ⚠️ **Limited**: Runtime rule customization, Docker Compose support, auto-fixing
61
61
  - 🚀 **Enhanced**: Issue normalization, Python integration, error parsing
62
62
 
63
+ ### [Black Analysis](./black-analysis.md)
64
+
65
+ **Python Code Formatter**
66
+
67
+ - ✅ **Preserved**: Core formatting, pyproject config, check and write flows
68
+ - ⚙️ **Pass-throughs**: `line_length`, `target_version`, `fast`, `preview`, `diff`
69
+ - 🚀 **Notes**: Cooperates with Ruff via Lintro post-check policy
70
+
63
71
  ## Analysis Framework
64
72
 
65
73
  Each analysis follows a consistent structure:
@@ -0,0 +1,111 @@
1
+ # Black Tool Analysis
2
+
3
+ ## Overview
4
+
5
+ Black is the Python code formatter that provides opinionated, deterministic
6
+ formatting. This analysis compares Lintro's wrapper implementation with the
7
+ core Black tool.
8
+
9
+ ## Core Tool Capabilities
10
+
11
+ Black provides a stable set of formatting capabilities:
12
+
13
+ - Code formatting with stable, minimal-diff output
14
+ - Check mode (`--check`) and write mode (default)
15
+ - Configuration via `pyproject.toml`
16
+ - Options such as `--line-length`, `--target-version`, `--fast`, `--preview`,
17
+ and `--diff`
18
+
19
+ ## Lintro Implementation Analysis
20
+
21
+ ### ✅ Preserved Features
22
+
23
+ - Formatting via Black with check and write flows
24
+ - Respect for Black's configuration in `pyproject.toml`
25
+ - Standard CLI behavior surfaced through the wrapper
26
+
27
+ ### ⚙️ Runtime Options (Pass-through)
28
+
29
+ Lintro exposes a subset of Black's CLI options for controlled pass-through:
30
+
31
+ - `line_length` → `--line-length`
32
+ - `target_version` → `--target-version`
33
+ - `fast` → `--fast`
34
+ - `preview` → `--preview`
35
+ - `diff` → `--diff` (when formatting during fix)
36
+
37
+ These can be provided via `--tool-options` or through
38
+ `[tool.lintro.black]` in `pyproject.toml`.
39
+
40
+ ```bash
41
+ # CLI overrides
42
+ lintro check . --tool-options "black:line_length=100,black:target_version=py313"
43
+ lintro format . --tool-options "black:fast=True,black:preview=True"
44
+ lintro format . --tool-options "black:diff=True"
45
+ ```
46
+
47
+ ```toml
48
+ [tool.lintro.black]
49
+ line_length = 100
50
+ target_version = "py313"
51
+ fast = false
52
+ preview = false
53
+ diff = false
54
+ ```
55
+
56
+ ### 🧩 Cooperation with Ruff
57
+
58
+ When Black is configured as a post-check in Lintro, Ruff focuses on linting by
59
+ default to avoid double-formatting. See the Ruff analysis for details on the
60
+ Ruff↔Black policy and how to override with `--tool-options`.
61
+
62
+ ## Usage Comparison
63
+
64
+ ### Core Black
65
+
66
+ ```bash
67
+ # Check
68
+ black --check src/
69
+
70
+ # Format
71
+ black src/
72
+
73
+ # With options
74
+ black --line-length 100 --target-version py313 src/
75
+ ```
76
+
77
+ ### Lintro Wrapper
78
+
79
+ ```python
80
+ from lintro.tools.implementations.tool_black import BlackTool
81
+
82
+ tool = BlackTool()
83
+ tool.set_options(line_length=100, target_version="py313")
84
+ result = tool.check(["src/"])
85
+ # or
86
+ result = tool.fix(["src/"])
87
+ ```
88
+
89
+ With CLI overrides:
90
+
91
+ ```bash
92
+ lintro check src/ --tool-options "black:line_length=100,black:target_version=py313"
93
+ ```
94
+
95
+ ## Configuration Strategy
96
+
97
+ - Primary configuration via Black's own `pyproject.toml`
98
+ - Optional overrides via `[tool.lintro.black]` and `--tool-options`
99
+ - Black can be used as a post-check via `[tool.lintro.post_checks]`
100
+
101
+ ## ⚠️ Limited/Missing Features
102
+
103
+ - No JSON output; Black output is parsed from text
104
+ - Only a curated subset of Black options are passed through at runtime
105
+ - No stdin-based formatting via wrapper (run core Black for advanced usage)
106
+
107
+ ## Recommendations
108
+
109
+ - Use Black post-checks to ensure final formatting consistency when combining
110
+ tools (Ruff for lint; Black for formatting)
111
+ - Prefer `pyproject.toml` for defaults; use `--tool-options` for ad-hoc runs
@@ -50,6 +50,27 @@ cmd = self._get_executable_command("ruff") + ["format"]
50
50
  - ✅ **Fix options**: `fix_only`, `unsafe_fixes`, `show_fixes`
51
51
  - ✅ **Formatting control**: `format` boolean to enable/disable formatting
52
52
 
53
+ ### Cooperation with Black (Policy)
54
+
55
+ When Black is configured as a post-check in Lintro, Ruff focuses on linting by
56
+ default:
57
+
58
+ - In `lintro format`, Ruff fixes lint issues while `format=False` unless
59
+ explicitly overridden via `--tool-options ruff:format=True`.
60
+ - In `lintro check`, Ruff runs lint checks with `format_check=False` unless
61
+ explicitly overridden via `--tool-options ruff:format_check=True`.
62
+
63
+ This avoids double-formatting and lets Black handle final formatting. You can
64
+ override either side via CLI or `[tool.lintro.ruff]` and `[tool.lintro.post_checks]`.
65
+
66
+ ```bash
67
+ # Force Ruff to format even with Black post-checks enabled
68
+ lintro format --tool-options "ruff:format=True"
69
+
70
+ # Force Ruff to include format-check during check
71
+ lintro check --tool-options "ruff:format_check=True"
72
+ ```
73
+
53
74
  ### ⚠️ Limited/Missing Features
54
75
 
55
76
  **Advanced Configuration:**
@@ -1,3 +1,3 @@
1
1
  """Lintro - A unified CLI core for code formatting, linting, and quality assurance."""
2
2
 
3
- __version__ = "0.5.3"
3
+ __version__ = "0.6.1"
@@ -54,7 +54,7 @@ class ActionlintTableDescriptor(TableDescriptor):
54
54
  issue.level,
55
55
  issue.code or "",
56
56
  issue.message,
57
- ]
57
+ ],
58
58
  )
59
59
  return rows
60
60
 
@@ -38,7 +38,7 @@ class BanditTableDescriptor(TableDescriptor):
38
38
  f"{severity_icon} {issue.issue_severity}",
39
39
  issue.issue_confidence,
40
40
  issue.issue_text,
41
- ]
41
+ ],
42
42
  )
43
43
  return rows
44
44
 
@@ -0,0 +1,48 @@
1
+ """Formatter for Black issues."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from lintro.formatters.core.table_descriptor import TableDescriptor
6
+ from lintro.formatters.styles.csv import CsvStyle
7
+ from lintro.formatters.styles.grid import GridStyle
8
+ from lintro.formatters.styles.html import HtmlStyle
9
+ from lintro.formatters.styles.json import JsonStyle
10
+ from lintro.formatters.styles.markdown import MarkdownStyle
11
+ from lintro.formatters.styles.plain import PlainStyle
12
+ from lintro.parsers.black.black_issue import BlackIssue
13
+ from lintro.utils.path_utils import normalize_file_path_for_display
14
+
15
+ FORMAT_MAP = {
16
+ "plain": PlainStyle(),
17
+ "grid": GridStyle(),
18
+ "markdown": MarkdownStyle(),
19
+ "html": HtmlStyle(),
20
+ "json": JsonStyle(),
21
+ "csv": CsvStyle(),
22
+ }
23
+
24
+
25
+ class BlackTableDescriptor(TableDescriptor):
26
+ def get_columns(self) -> list[str]:
27
+ return ["File", "Message"]
28
+
29
+ def get_rows(self, issues: list[BlackIssue]) -> list[list[str]]:
30
+ rows: list[list[str]] = []
31
+ for issue in issues:
32
+ rows.append(
33
+ [
34
+ normalize_file_path_for_display(issue.file),
35
+ issue.message,
36
+ ],
37
+ )
38
+ return rows
39
+
40
+
41
+ def format_black_issues(issues: list[BlackIssue], format: str = "grid") -> str:
42
+ descriptor = BlackTableDescriptor()
43
+ formatter = FORMAT_MAP.get(format, GridStyle())
44
+ columns = descriptor.get_columns()
45
+ rows = descriptor.get_rows(issues)
46
+ if format == "json":
47
+ return formatter.format(columns=columns, rows=rows, tool_name="black")
48
+ return formatter.format(columns=columns, rows=rows)
@@ -63,7 +63,9 @@ def format_darglint_issues(
63
63
  # For JSON format, pass tool name
64
64
  if format == "json":
65
65
  formatted_table = formatter.format(
66
- columns=columns, rows=rows, tool_name="darglint"
66
+ columns=columns,
67
+ rows=rows,
68
+ tool_name="darglint",
67
69
  )
68
70
  else:
69
71
  # For other formats, use standard formatting
@@ -0,0 +1,22 @@
1
+ """Black issue models.
2
+
3
+ This module defines lightweight dataclasses used to represent Black findings
4
+ in a normalized form that Lintro formatters can consume.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class BlackIssue:
14
+ """Represents a Black formatting issue.
15
+
16
+ Attributes:
17
+ file: Path to the file with a formatting difference.
18
+ message: Short human-readable description (e.g., "Would reformat file").
19
+ """
20
+
21
+ file: str
22
+ message: str
@@ -0,0 +1,90 @@
1
+ """Parser for Black output.
2
+
3
+ Black commonly emits terse messages like:
4
+ - "would reformat foo.py" (check mode with --check)
5
+ - "reformatted foo.py" (fix mode)
6
+ - a summary line like "1 file would be reformatted" or
7
+ "2 files reformatted" (with no per-file lines in some environments).
8
+
9
+ We normalize items into ``BlackIssue`` objects so the table formatter can
10
+ render consistent rows. When only a summary is present, we synthesize one
11
+ ``BlackIssue`` per counted file with ``file`` set to "<unknown>" so totals
12
+ remain accurate across environments.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from collections.abc import Iterable
19
+
20
+ from lintro.parsers.black.black_issue import BlackIssue
21
+
22
+ _WOULD_REFORMAT = re.compile(r"^would reformat\s+(?P<file>.+)$", re.IGNORECASE)
23
+ _REFORMATTED = re.compile(r"^reformatted\s+(?P<file>.+)$", re.IGNORECASE)
24
+ _SUMMARY_WOULD = re.compile(
25
+ r"(?P<count>\d+)\s+file(?:s)?\s+would\s+be\s+reformatted\.?",
26
+ re.IGNORECASE,
27
+ )
28
+ _SUMMARY_REFORMATTED = re.compile(
29
+ r"(?P<count>\d+)\s+file(?:s)?\s+reformatted\.?",
30
+ re.IGNORECASE,
31
+ )
32
+
33
+
34
+ def _iter_issue_lines(lines: Iterable[str]) -> Iterable[str]:
35
+ for line in lines:
36
+ s = line.strip()
37
+ if not s:
38
+ continue
39
+ yield s
40
+
41
+
42
+ def parse_black_output(output: str) -> list[BlackIssue]:
43
+ """Parse Black CLI output into a list of ``BlackIssue`` objects.
44
+
45
+ Args:
46
+ output: Raw stdout+stderr from a Black invocation.
47
+
48
+ Returns:
49
+ list[BlackIssue]: Per-file issues indicating formatting diffs. If only
50
+ a summary is present (no per-file lines), returns a synthesized list
51
+ sized to the summary count with ``file`` set to "<unknown>".
52
+ """
53
+ if not output:
54
+ return []
55
+
56
+ issues: list[BlackIssue] = []
57
+ for line in _iter_issue_lines(output.splitlines()):
58
+ m = _WOULD_REFORMAT.match(line)
59
+ if m:
60
+ issues.append(
61
+ BlackIssue(file=m.group("file"), message="Would reformat file"),
62
+ )
63
+ continue
64
+ m = _REFORMATTED.match(line)
65
+ if m:
66
+ issues.append(BlackIssue(file=m.group("file"), message="Reformatted file"))
67
+ continue
68
+
69
+ # Some environments (e.g., CI) may emit only a summary line without listing
70
+ # per-file entries. In that case, synthesize issues so counts remain
71
+ # consistent across environments.
72
+ if not issues:
73
+ m_sum = _SUMMARY_WOULD.search(output)
74
+ if not m_sum:
75
+ m_sum = _SUMMARY_REFORMATTED.search(output)
76
+ if m_sum:
77
+ try:
78
+ count = int(m_sum.group("count"))
79
+ except Exception:
80
+ count = 0
81
+ if count > 0:
82
+ for _ in range(count):
83
+ issues.append(
84
+ BlackIssue(
85
+ file="<unknown>",
86
+ message="Formatting change detected",
87
+ ),
88
+ )
89
+
90
+ return issues
@@ -56,7 +56,7 @@ def parse_darglint_output(output: str) -> list[DarglintIssue]:
56
56
  line=int(line_num),
57
57
  code=code,
58
58
  message=full_message,
59
- )
59
+ ),
60
60
  )
61
61
  i = j
62
62
  return issues
@@ -32,7 +32,7 @@ def parse_hadolint_output(output: str) -> list[HadolintIssue]:
32
32
 
33
33
  # Pattern for hadolint output: filename:line code level: message
34
34
  pattern: re.Pattern[str] = re.compile(
35
- r"^(.+?):(\d+)\s+([A-Z]+\d+)\s+(error|warning|info|style):\s+(.+)$"
35
+ r"^(.+?):(\d+)\s+([A-Z]+\d+)\s+(error|warning|info|style):\s+(.+)$",
36
36
  )
37
37
 
38
38
  lines: list[str] = output.splitlines()
@@ -59,7 +59,7 @@ def parse_hadolint_output(output: str) -> list[HadolintIssue]:
59
59
  level=level,
60
60
  code=code,
61
61
  message=message.strip(),
62
- )
62
+ ),
63
63
  )
64
64
 
65
65
  return issues