thailint 0.15.0__tar.gz → 0.15.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 (227) hide show
  1. {thailint-0.15.0 → thailint-0.15.2}/PKG-INFO +6 -4
  2. {thailint-0.15.0 → thailint-0.15.2}/README.md +3 -2
  3. {thailint-0.15.0 → thailint-0.15.2}/pyproject.toml +3 -1
  4. thailint-0.15.2/src/analyzers/rust_base.py +155 -0
  5. thailint-0.15.2/src/analyzers/rust_context.py +141 -0
  6. {thailint-0.15.0 → thailint-0.15.2}/src/cli/config.py +6 -4
  7. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/code_patterns.py +64 -16
  8. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/code_smells.py +23 -14
  9. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/documentation.py +5 -3
  10. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/performance.py +23 -10
  11. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/shared.py +22 -6
  12. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/structure.py +13 -4
  13. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/structure_quality.py +9 -4
  14. {thailint-0.15.0 → thailint-0.15.2}/src/cli/utils.py +4 -4
  15. {thailint-0.15.0 → thailint-0.15.2}/src/config.py +34 -21
  16. thailint-0.15.2/src/core/python_lint_rule.py +101 -0
  17. {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/ignore.py +2 -1
  18. thailint-0.15.2/src/linters/cqs/__init__.py +54 -0
  19. thailint-0.15.2/src/linters/cqs/config.py +55 -0
  20. thailint-0.15.2/src/linters/cqs/function_analyzer.py +201 -0
  21. thailint-0.15.2/src/linters/cqs/input_detector.py +139 -0
  22. thailint-0.15.2/src/linters/cqs/linter.py +159 -0
  23. thailint-0.15.2/src/linters/cqs/output_detector.py +84 -0
  24. thailint-0.15.2/src/linters/cqs/python_analyzer.py +54 -0
  25. thailint-0.15.2/src/linters/cqs/types.py +82 -0
  26. thailint-0.15.2/src/linters/cqs/typescript_cqs_analyzer.py +61 -0
  27. thailint-0.15.2/src/linters/cqs/typescript_function_analyzer.py +192 -0
  28. thailint-0.15.2/src/linters/cqs/typescript_input_detector.py +203 -0
  29. thailint-0.15.2/src/linters/cqs/typescript_output_detector.py +117 -0
  30. thailint-0.15.2/src/linters/cqs/violation_builder.py +94 -0
  31. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_value_extractor.py +2 -1
  32. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/linter.py +2 -1
  33. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/linter.py +6 -6
  34. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/pattern_validator.py +6 -5
  35. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/rule_checker.py +10 -5
  36. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/config.py +5 -3
  37. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/python_analyzer.py +5 -1
  38. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/types.py +2 -1
  39. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lbyl/__init__.py +3 -1
  40. thailint-0.15.2/src/linters/lbyl/linter.py +67 -0
  41. thailint-0.15.2/src/linters/lbyl/pattern_detectors/__init__.py +53 -0
  42. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lbyl/pattern_detectors/base.py +24 -7
  43. thailint-0.15.2/src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
  44. thailint-0.15.2/src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
  45. thailint-0.15.2/src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
  46. thailint-0.15.2/src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
  47. thailint-0.15.2/src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
  48. thailint-0.15.2/src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
  49. thailint-0.15.2/src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
  50. thailint-0.15.2/src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
  51. thailint-0.15.2/src/linters/lbyl/python_analyzer.py +215 -0
  52. thailint-0.15.2/src/linters/lbyl/violation_builder.py +354 -0
  53. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/heuristics.py +47 -14
  54. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/typescript_metrics_calculator.py +34 -10
  55. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/ignore_checker.py +4 -6
  56. {thailint-0.15.0 → thailint-0.15.2}/src/orchestrator/language_detector.py +5 -3
  57. thailint-0.15.0/src/linters/lbyl/pattern_detectors/__init__.py +0 -25
  58. {thailint-0.15.0 → thailint-0.15.2}/CHANGELOG.md +0 -0
  59. {thailint-0.15.0 → thailint-0.15.2}/LICENSE +0 -0
  60. {thailint-0.15.0 → thailint-0.15.2}/src/__init__.py +0 -0
  61. {thailint-0.15.0 → thailint-0.15.2}/src/analyzers/__init__.py +0 -0
  62. {thailint-0.15.0 → thailint-0.15.2}/src/analyzers/ast_utils.py +0 -0
  63. {thailint-0.15.0 → thailint-0.15.2}/src/analyzers/typescript_base.py +0 -0
  64. {thailint-0.15.0 → thailint-0.15.2}/src/api.py +0 -0
  65. {thailint-0.15.0 → thailint-0.15.2}/src/cli/__init__.py +0 -0
  66. {thailint-0.15.0 → thailint-0.15.2}/src/cli/__main__.py +0 -0
  67. {thailint-0.15.0 → thailint-0.15.2}/src/cli/config_merge.py +0 -0
  68. {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/__init__.py +0 -0
  69. {thailint-0.15.0 → thailint-0.15.2}/src/cli/main.py +0 -0
  70. {thailint-0.15.0 → thailint-0.15.2}/src/cli_main.py +0 -0
  71. {thailint-0.15.0 → thailint-0.15.2}/src/core/__init__.py +0 -0
  72. {thailint-0.15.0 → thailint-0.15.2}/src/core/base.py +0 -0
  73. {thailint-0.15.0 → thailint-0.15.2}/src/core/cli_utils.py +0 -0
  74. {thailint-0.15.0 → thailint-0.15.2}/src/core/config_parser.py +0 -0
  75. {thailint-0.15.0 → thailint-0.15.2}/src/core/constants.py +0 -0
  76. {thailint-0.15.0 → thailint-0.15.2}/src/core/linter_utils.py +0 -0
  77. {thailint-0.15.0 → thailint-0.15.2}/src/core/registry.py +0 -0
  78. {thailint-0.15.0 → thailint-0.15.2}/src/core/rule_discovery.py +0 -0
  79. {thailint-0.15.0 → thailint-0.15.2}/src/core/types.py +0 -0
  80. {thailint-0.15.0 → thailint-0.15.2}/src/core/violation_builder.py +0 -0
  81. {thailint-0.15.0 → thailint-0.15.2}/src/core/violation_utils.py +0 -0
  82. {thailint-0.15.0 → thailint-0.15.2}/src/formatters/__init__.py +0 -0
  83. {thailint-0.15.0 → thailint-0.15.2}/src/formatters/sarif.py +0 -0
  84. {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/__init__.py +0 -0
  85. {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/directive_markers.py +0 -0
  86. {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/loader.py +0 -0
  87. {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/pattern_utils.py +0 -0
  88. {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/rule_matcher.py +0 -0
  89. {thailint-0.15.0 → thailint-0.15.2}/src/linters/__init__.py +0 -0
  90. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/__init__.py +0 -0
  91. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/any_all_analyzer.py +0 -0
  92. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/ast_utils.py +0 -0
  93. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/config.py +0 -0
  94. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/continue_analyzer.py +0 -0
  95. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/detector.py +0 -0
  96. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/filter_map_analyzer.py +0 -0
  97. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/linter.py +0 -0
  98. {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/suggestion_builder.py +0 -0
  99. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/__init__.py +0 -0
  100. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/base_token_analyzer.py +0 -0
  101. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/block_filter.py +0 -0
  102. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/block_grouper.py +0 -0
  103. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/cache.py +0 -0
  104. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/cache_query.py +0 -0
  105. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/config.py +0 -0
  106. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/config_loader.py +0 -0
  107. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/constant.py +0 -0
  108. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/constant_matcher.py +0 -0
  109. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/constant_violation_builder.py +0 -0
  110. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/deduplicator.py +0 -0
  111. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/duplicate_storage.py +0 -0
  112. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/file_analyzer.py +0 -0
  113. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/inline_ignore.py +0 -0
  114. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/linter.py +0 -0
  115. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/python_analyzer.py +0 -0
  116. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/python_constant_extractor.py +0 -0
  117. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/single_statement_detector.py +0 -0
  118. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/storage_initializer.py +0 -0
  119. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/token_hasher.py +0 -0
  120. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_analyzer.py +0 -0
  121. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_constant_extractor.py +0 -0
  122. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_statement_detector.py +0 -0
  123. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/violation_builder.py +0 -0
  124. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/violation_filter.py +0 -0
  125. {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/violation_generator.py +0 -0
  126. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/__init__.py +0 -0
  127. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/atemporal_detector.py +0 -0
  128. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/base_parser.py +0 -0
  129. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/bash_parser.py +0 -0
  130. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/config.py +0 -0
  131. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/css_parser.py +0 -0
  132. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/field_validator.py +0 -0
  133. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/markdown_parser.py +0 -0
  134. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/python_parser.py +0 -0
  135. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/typescript_parser.py +0 -0
  136. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/violation_builder.py +0 -0
  137. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/__init__.py +0 -0
  138. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/config_loader.py +0 -0
  139. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/directory_matcher.py +0 -0
  140. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/path_resolver.py +0 -0
  141. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/pattern_matcher.py +0 -0
  142. {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/violation_factory.py +0 -0
  143. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/__init__.py +0 -0
  144. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/directive_utils.py +0 -0
  145. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/header_parser.py +0 -0
  146. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/linter.py +0 -0
  147. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/matcher.py +0 -0
  148. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/rule_id_utils.py +0 -0
  149. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/skip_detector.py +0 -0
  150. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/typescript_analyzer.py +0 -0
  151. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/violation_builder.py +0 -0
  152. {thailint-0.15.0 → thailint-0.15.2}/src/linters/lbyl/config.py +0 -0
  153. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/__init__.py +0 -0
  154. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/config.py +0 -0
  155. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/context_analyzer.py +0 -0
  156. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/linter.py +0 -0
  157. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/python_analyzer.py +0 -0
  158. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
  159. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/typescript_ignore_checker.py +0 -0
  160. {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/violation_builder.py +0 -0
  161. {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/__init__.py +0 -0
  162. {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/config.py +0 -0
  163. {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/linter.py +0 -0
  164. {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/python_analyzer.py +0 -0
  165. {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/violation_builder.py +0 -0
  166. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/__init__.py +0 -0
  167. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/config.py +0 -0
  168. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/linter.py +0 -0
  169. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/python_analyzer.py +0 -0
  170. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/typescript_analyzer.py +0 -0
  171. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/typescript_function_extractor.py +0 -0
  172. {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/violation_builder.py +0 -0
  173. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/__init__.py +0 -0
  174. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/config.py +0 -0
  175. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/constants.py +0 -0
  176. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/linter.py +0 -0
  177. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/python_analyzer.py +0 -0
  178. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/regex_analyzer.py +0 -0
  179. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/regex_linter.py +0 -0
  180. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/typescript_analyzer.py +0 -0
  181. {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/violation_builder.py +0 -0
  182. {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/__init__.py +0 -0
  183. {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/config.py +0 -0
  184. {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/linter.py +0 -0
  185. {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/python_analyzer.py +0 -0
  186. {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/typescript_analyzer.py +0 -0
  187. {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/violation_builder.py +0 -0
  188. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/__init__.py +0 -0
  189. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/class_analyzer.py +0 -0
  190. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/config.py +0 -0
  191. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/linter.py +0 -0
  192. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/metrics_evaluator.py +0 -0
  193. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/python_analyzer.py +0 -0
  194. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/typescript_analyzer.py +0 -0
  195. {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/violation_builder.py +0 -0
  196. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/__init__.py +0 -0
  197. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/config.py +0 -0
  198. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/linter.py +0 -0
  199. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/python_analyzer.py +0 -0
  200. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/__init__.py +0 -0
  201. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/config.py +0 -0
  202. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/context_filter.py +0 -0
  203. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/function_call_violation_builder.py +0 -0
  204. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/ignore_utils.py +0 -0
  205. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/linter.py +0 -0
  206. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/__init__.py +0 -0
  207. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/analyzer.py +0 -0
  208. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/call_tracker.py +0 -0
  209. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/comparison_tracker.py +0 -0
  210. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/condition_extractor.py +0 -0
  211. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/conditional_detector.py +0 -0
  212. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/constants.py +0 -0
  213. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/match_analyzer.py +0 -0
  214. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/validation_detector.py +0 -0
  215. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/variable_extractor.py +0 -0
  216. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/storage.py +0 -0
  217. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/storage_initializer.py +0 -0
  218. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/__init__.py +0 -0
  219. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/analyzer.py +0 -0
  220. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/call_tracker.py +0 -0
  221. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/comparison_tracker.py +0 -0
  222. {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/violation_generator.py +0 -0
  223. {thailint-0.15.0 → thailint-0.15.2}/src/orchestrator/__init__.py +0 -0
  224. {thailint-0.15.0 → thailint-0.15.2}/src/orchestrator/core.py +0 -0
  225. {thailint-0.15.0 → thailint-0.15.2}/src/templates/thailint_config_template.yaml +0 -0
  226. {thailint-0.15.0 → thailint-0.15.2}/src/utils/__init__.py +0 -0
  227. {thailint-0.15.0 → thailint-0.15.2}/src/utils/project_root.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thailint
3
- Version: 0.15.0
3
+ Version: 0.15.2
4
4
  Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
5
  License: MIT
6
6
  License-File: LICENSE
7
- Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python,performance,typescript
7
+ Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python,performance,typescript,rust
8
8
  Author: Steve Jackson
9
9
  Requires-Python: >=3.11,<4.0
10
10
  Classifier: Development Status :: 4 - Beta
@@ -27,6 +27,7 @@ Requires-Dist: click (>=8.1.0,<9.0.0)
27
27
  Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
28
28
  Requires-Dist: pyyaml (>=6.0,<7.0)
29
29
  Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
30
+ Requires-Dist: tree-sitter-rust (>=0.23.2,<0.24.0)
30
31
  Requires-Dist: tree-sitter-typescript (>=0.23.2,<0.24.0)
31
32
  Project-URL: Documentation, https://thai-lint.readthedocs.io/
32
33
  Project-URL: Homepage, https://github.com/be-wise-be-kind/thai-lint
@@ -37,7 +38,7 @@ Description-Content-Type: text/markdown
37
38
 
38
39
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
40
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
- [![PyPI](https://img.shields.io/pypi/v/thai-lint)](https://pypi.org/project/thai-lint/)
41
+ [![PyPI](https://img.shields.io/pypi/v/thailint)](https://pypi.org/project/thailint/)
41
42
  [![Documentation](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/)
42
43
 
43
44
  **The AI Linter** - Catch the mistakes AI coding assistants keep making.
@@ -47,7 +48,7 @@ thailint detects anti-patterns that AI tools frequently introduce: duplicate cod
47
48
  ## Installation
48
49
 
49
50
  ```bash
50
- pip install thai-lint
51
+ pip install thailint
51
52
  ```
52
53
 
53
54
  Or with Docker:
@@ -84,6 +85,7 @@ That's it. See violations, fix them, ship better code.
84
85
  | **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
85
86
  | **Print Statements** | Debug prints left in code | `thailint print-statements src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/print-statements-linter/) |
86
87
  | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
88
+ | **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
87
89
 
88
90
  ## Configuration
89
91
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
5
- [![PyPI](https://img.shields.io/pypi/v/thai-lint)](https://pypi.org/project/thai-lint/)
5
+ [![PyPI](https://img.shields.io/pypi/v/thailint)](https://pypi.org/project/thailint/)
6
6
  [![Documentation](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/)
7
7
 
8
8
  **The AI Linter** - Catch the mistakes AI coding assistants keep making.
@@ -12,7 +12,7 @@ thailint detects anti-patterns that AI tools frequently introduce: duplicate cod
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- pip install thai-lint
15
+ pip install thailint
16
16
  ```
17
17
 
18
18
  Or with Docker:
@@ -49,6 +49,7 @@ That's it. See violations, fix them, ship better code.
49
49
  | **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
50
50
  | **Print Statements** | Debug prints left in code | `thailint print-statements src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/print-statements-linter/) |
51
51
  | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
52
+ | **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
52
53
 
53
54
  ## Configuration
54
55
 
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
17
17
 
18
18
  [tool.poetry]
19
19
  name = "thailint"
20
- version = "0.15.0"
20
+ version = "0.15.2"
21
21
  description = "The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages"
22
22
  authors = ["Steve Jackson"]
23
23
  license = "MIT"
@@ -38,6 +38,7 @@ keywords = [
38
38
  "python",
39
39
  "performance",
40
40
  "typescript",
41
+ "rust",
41
42
  ]
42
43
  classifiers = [
43
44
  "Development Status :: 4 - Beta",
@@ -75,6 +76,7 @@ click = "^8.1.0"
75
76
  pyyaml = "^6.0"
76
77
  tree-sitter = "^0.25.2"
77
78
  tree-sitter-typescript = "^0.23.2"
79
+ tree-sitter-rust = "^0.23.2"
78
80
  pyprojroot = "^0.3.0"
79
81
 
80
82
  [tool.poetry.group.dev.dependencies]
@@ -0,0 +1,155 @@
1
+ """
2
+ Purpose: Base class for Rust AST analysis with tree-sitter parsing
3
+
4
+ Scope: Common tree-sitter initialization, parsing, and traversal utilities for Rust
5
+
6
+ Overview: Provides shared infrastructure for Rust code analysis using tree-sitter parser.
7
+ Implements common tree-sitter initialization with language setup and parser configuration.
8
+ Provides reusable parsing methods that convert Rust source to AST nodes. Includes
9
+ shared traversal utilities for walking AST trees recursively and finding nodes by type.
10
+ Delegates context-specific detection (test functions, async functions) to rust_context
11
+ module. Serves as foundation for specialized Rust analyzers (unwrap abuse, clone abuse).
12
+
13
+ Dependencies: tree-sitter, tree-sitter-rust (optional), src.analyzers.rust_context
14
+
15
+ Exports: RustBaseAnalyzer class with parsing and traversal utilities,
16
+ TREE_SITTER_RUST_AVAILABLE constant for runtime detection
17
+
18
+ Interfaces: parse_rust(code), walk_tree(node, node_type), extract_node_text(node),
19
+ is_inside_test(node), is_async_function(node)
20
+
21
+ Implementation: Tree-sitter parser singleton, recursive AST traversal, composition pattern
22
+ with rust_context helpers
23
+
24
+ Suppressions:
25
+ - type:ignore[assignment]: Tree-sitter RUST_PARSER fallback when import fails
26
+ - type:ignore[assignment,misc]: Tree-sitter Node type alias (optional dependency fallback)
27
+ """
28
+
29
+ from typing import Any
30
+
31
+ from src.analyzers import rust_context
32
+
33
+ try:
34
+ import tree_sitter_rust as tsrust
35
+ from tree_sitter import Language, Node, Parser
36
+
37
+ RUST_LANGUAGE = Language(tsrust.language())
38
+ RUST_PARSER = Parser(RUST_LANGUAGE)
39
+ TREE_SITTER_RUST_AVAILABLE = True
40
+ except ImportError:
41
+ TREE_SITTER_RUST_AVAILABLE = False
42
+ RUST_PARSER = None # type: ignore[assignment]
43
+ Node = Any # type: ignore[assignment,misc]
44
+
45
+
46
+ class RustBaseAnalyzer:
47
+ """Base analyzer for Rust code using tree-sitter."""
48
+
49
+ def __init__(self) -> None:
50
+ """Initialize Rust base analyzer."""
51
+ self.tree_sitter_available = TREE_SITTER_RUST_AVAILABLE
52
+
53
+ def parse_rust(self, code: str) -> Node | None:
54
+ """Parse Rust code to AST using tree-sitter.
55
+
56
+ Args:
57
+ code: Rust source code to parse
58
+
59
+ Returns:
60
+ Tree-sitter AST root node, or None if parsing fails or tree-sitter unavailable
61
+ """
62
+ if not TREE_SITTER_RUST_AVAILABLE or RUST_PARSER is None:
63
+ return None
64
+
65
+ tree = RUST_PARSER.parse(bytes(code, "utf8"))
66
+ return tree.root_node
67
+
68
+ def walk_tree(self, node: Node, node_type: str) -> list[Node]:
69
+ """Find all nodes of a specific type in the AST.
70
+
71
+ Recursively walks the tree and collects all nodes matching the given type.
72
+
73
+ Args:
74
+ node: Root tree-sitter node to search from
75
+ node_type: Tree-sitter node type to find (e.g., "function_item")
76
+
77
+ Returns:
78
+ List of all matching nodes
79
+ """
80
+ if not TREE_SITTER_RUST_AVAILABLE or node is None:
81
+ return []
82
+
83
+ nodes: list[Node] = []
84
+ self._walk_tree_recursive(node, node_type, nodes)
85
+ return nodes
86
+
87
+ def _walk_tree_recursive(self, node: Node, node_type: str, nodes: list[Node]) -> None:
88
+ """Recursively walk tree to find nodes of specific type.
89
+
90
+ Args:
91
+ node: Current tree-sitter node
92
+ node_type: Node type to find
93
+ nodes: List to accumulate matching nodes
94
+ """
95
+ if node.type == node_type:
96
+ nodes.append(node)
97
+
98
+ for child in node.children:
99
+ self._walk_tree_recursive(child, node_type, nodes)
100
+
101
+ def extract_node_text(self, node: Node) -> str:
102
+ """Extract text content from a tree-sitter node.
103
+
104
+ Args:
105
+ node: Tree-sitter node
106
+
107
+ Returns:
108
+ Decoded text content of the node
109
+ """
110
+ text = node.text
111
+ if text is None:
112
+ return ""
113
+ return text.decode()
114
+
115
+ def extract_identifier_name(self, node: Node) -> str:
116
+ """Extract identifier name from node children.
117
+
118
+ Common pattern for extracting names from function/struct declarations.
119
+
120
+ Args:
121
+ node: Node to extract identifier from
122
+
123
+ Returns:
124
+ Identifier name or "anonymous" fallback
125
+ """
126
+ for child in node.children:
127
+ if child.type == "identifier":
128
+ return self.extract_node_text(child)
129
+ return "anonymous"
130
+
131
+ def is_inside_test(self, node: Node) -> bool:
132
+ """Check if node is inside a test function or module.
133
+
134
+ Delegates to rust_context module for implementation.
135
+
136
+ Args:
137
+ node: Tree-sitter node to check
138
+
139
+ Returns:
140
+ True if the node is inside a test function or test module
141
+ """
142
+ return rust_context.is_inside_test(node)
143
+
144
+ def is_async_function(self, node: Node) -> bool:
145
+ """Check if a function_item is async.
146
+
147
+ Delegates to rust_context module for implementation.
148
+
149
+ Args:
150
+ node: Function item node to check
151
+
152
+ Returns:
153
+ True if the function is declared as async
154
+ """
155
+ return rust_context.is_async_function(node)
@@ -0,0 +1,141 @@
1
+ """
2
+ Purpose: Rust-specific AST context detection utilities
3
+
4
+ Scope: Helper functions for detecting test contexts and async functions in Rust code
5
+
6
+ Overview: Provides standalone helper functions for analyzing Rust AST context information.
7
+ Includes functions for detecting if code is inside a test function or module by checking
8
+ for #[test] or #[cfg(test)] attributes on preceding siblings, and for detecting async
9
+ functions by checking for function_modifiers. These utilities are designed to be used
10
+ in composition with RustBaseAnalyzer for Rust-specific lint rules that need to understand
11
+ code context.
12
+
13
+ Dependencies: tree-sitter (for Node type when available)
14
+
15
+ Exports: is_inside_test, is_async_function, has_test_attribute, has_cfg_test_attribute
16
+
17
+ Interfaces: All functions take tree-sitter Node objects and return bool
18
+
19
+ Implementation: Sibling-based attribute lookup for Rust AST structure, iterative parent
20
+ traversal for context detection
21
+
22
+ Suppressions:
23
+ - misc,assignment: Node type alias when tree-sitter optional dependency unavailable
24
+ """
25
+
26
+ from typing import Any
27
+
28
+ try:
29
+ from tree_sitter import Node
30
+
31
+ TREE_SITTER_AVAILABLE = True
32
+ except ImportError:
33
+ TREE_SITTER_AVAILABLE = False
34
+ Node = Any # type: ignore[misc,assignment]
35
+
36
+
37
+ def _get_node_text(node: Node) -> str:
38
+ """Get decoded text from a node.
39
+
40
+ Args:
41
+ node: Tree-sitter node
42
+
43
+ Returns:
44
+ Decoded text content
45
+ """
46
+ return node.text.decode() if node.text else ""
47
+
48
+
49
+ def has_test_attribute(function_node: Node) -> bool:
50
+ """Check if a function has #[test] attribute as preceding sibling.
51
+
52
+ Args:
53
+ function_node: Function item node
54
+
55
+ Returns:
56
+ True if function has #[test] attribute
57
+ """
58
+ prev_sibling = function_node.prev_sibling
59
+ while prev_sibling is not None and prev_sibling.type == "attribute_item":
60
+ if "test" in _get_node_text(prev_sibling):
61
+ return True
62
+ prev_sibling = prev_sibling.prev_sibling
63
+ return False
64
+
65
+
66
+ def has_cfg_test_attribute(mod_node: Node) -> bool:
67
+ """Check if a module has #[cfg(test)] attribute as preceding sibling.
68
+
69
+ Args:
70
+ mod_node: Module item node
71
+
72
+ Returns:
73
+ True if module has #[cfg(test)] attribute
74
+ """
75
+ prev_sibling = mod_node.prev_sibling
76
+ while prev_sibling is not None and prev_sibling.type == "attribute_item":
77
+ if "cfg(test)" in _get_node_text(prev_sibling):
78
+ return True
79
+ prev_sibling = prev_sibling.prev_sibling
80
+ return False
81
+
82
+
83
+ def is_inside_test(node: Node) -> bool:
84
+ """Check if node is inside a test function or module.
85
+
86
+ Walks up the tree looking for #[test] or #[cfg(test)] attributes.
87
+
88
+ Args:
89
+ node: Tree-sitter node to check
90
+
91
+ Returns:
92
+ True if the node is inside a test function or test module
93
+ """
94
+ current: Node | None = node
95
+ while current is not None:
96
+ if _is_test_context(current):
97
+ return True
98
+ current = current.parent
99
+ return False
100
+
101
+
102
+ def _is_test_context(node: Node) -> bool:
103
+ """Check if a node represents a test context.
104
+
105
+ Args:
106
+ node: Node to check
107
+
108
+ Returns:
109
+ True if node is a test function or test module
110
+ """
111
+ if node.type == "function_item":
112
+ return has_test_attribute(node)
113
+ if node.type == "mod_item":
114
+ return has_cfg_test_attribute(node)
115
+ return False
116
+
117
+
118
+ def is_async_function(node: Node) -> bool:
119
+ """Check if a function_item is async.
120
+
121
+ Args:
122
+ node: Function item node to check
123
+
124
+ Returns:
125
+ True if the function is declared as async
126
+ """
127
+ return any(
128
+ child.type == "function_modifiers" and _has_async_modifier(child) for child in node.children
129
+ )
130
+
131
+
132
+ def _has_async_modifier(modifiers_node: Node) -> bool:
133
+ """Check if function_modifiers node contains async keyword.
134
+
135
+ Args:
136
+ modifiers_node: The function_modifiers node
137
+
138
+ Returns:
139
+ True if async keyword is present
140
+ """
141
+ return any(modifier.type == "async" for modifier in modifiers_node.children)
@@ -150,12 +150,14 @@ def config_get(ctx: click.Context, key: str) -> None:
150
150
 
151
151
  def _convert_value_type(value: str) -> bool | int | float | str:
152
152
  """Convert string value to appropriate type."""
153
+ from contextlib import suppress
154
+
153
155
  if value.lower() in ["true", "false"]:
154
156
  return value.lower() == "true"
155
- if value.isdigit():
156
- return int(value)
157
- if value.replace(".", "", 1).isdigit() and value.count(".") == 1:
158
- return float(value)
157
+ # Use EAFP pattern for numeric conversion
158
+ for converter in (int, float):
159
+ with suppress(ValueError):
160
+ return converter(value)
159
161
  return value
160
162
 
161
163
 
@@ -1,17 +1,18 @@
1
1
  """
2
- Purpose: CLI commands for code pattern linters (print-statements, method-property, stateless-class, lazy-ignores)
2
+ Purpose: CLI commands for code pattern linters (print-statements, method-property, stateless-class, lazy-ignores, lbyl)
3
3
 
4
4
  Scope: Commands that detect code patterns and anti-patterns in Python code
5
5
 
6
6
  Overview: Provides CLI commands for code pattern linting: print-statements detects print() and
7
7
  console.log calls that should use proper logging, method-property finds methods that should be
8
8
  @property decorators, stateless-class detects classes without state that should be module
9
- functions, and lazy-ignores detects unjustified linting suppressions. Each command supports
10
- standard options (config, format, recursive) and integrates with the orchestrator for execution.
9
+ functions, lazy-ignores detects unjustified linting suppressions, and lbyl detects Look Before
10
+ You Leap anti-patterns. Each command supports standard options (config, format, recursive) and
11
+ integrates with the orchestrator for execution.
11
12
 
12
13
  Dependencies: click for CLI framework, src.cli.main for CLI group, src.cli.utils for shared utilities
13
14
 
14
- Exports: print_statements command, method_property command, stateless_class command, lazy_ignores command
15
+ Exports: print_statements, method_property, stateless_class, lazy_ignores, lbyl commands
15
16
 
16
17
  Interfaces: Click CLI commands registered to main CLI group
17
18
 
@@ -48,10 +49,10 @@ def _setup_print_statements_orchestrator(
48
49
 
49
50
 
50
51
  def _run_print_statements_lint(
51
- orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool
52
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
52
53
  ) -> list[Violation]:
53
54
  """Execute print-statements lint on files or directories."""
54
- all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive)
55
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
55
56
  return [v for v in all_violations if "print-statement" in v.rule_id]
56
57
 
57
58
 
@@ -62,7 +63,7 @@ def _execute_print_statements_lint(params: ExecuteParams) -> NoReturn:
62
63
  params.path_objs, params.config_file, params.verbose, params.project_root
63
64
  )
64
65
  print_statements_violations = _run_print_statements_lint(
65
- orchestrator, params.path_objs, params.recursive
66
+ orchestrator, params.path_objs, params.recursive, params.parallel
66
67
  )
67
68
 
68
69
  if params.verbose:
@@ -94,10 +95,10 @@ def _setup_method_property_orchestrator(
94
95
 
95
96
 
96
97
  def _run_method_property_lint(
97
- orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool
98
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
98
99
  ) -> list[Violation]:
99
100
  """Execute method-property lint on files or directories."""
100
- all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive)
101
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
101
102
  return [v for v in all_violations if "method-property" in v.rule_id]
102
103
 
103
104
 
@@ -108,7 +109,7 @@ def _execute_method_property_lint(params: ExecuteParams) -> NoReturn:
108
109
  params.path_objs, params.config_file, params.verbose, params.project_root
109
110
  )
110
111
  method_property_violations = _run_method_property_lint(
111
- orchestrator, params.path_objs, params.recursive
112
+ orchestrator, params.path_objs, params.recursive, params.parallel
112
113
  )
113
114
 
114
115
  if params.verbose:
@@ -141,10 +142,10 @@ def _setup_stateless_class_orchestrator(
141
142
 
142
143
 
143
144
  def _run_stateless_class_lint(
144
- orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool
145
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
145
146
  ) -> list[Violation]:
146
147
  """Execute stateless-class lint on files or directories."""
147
- all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive)
148
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
148
149
  return [v for v in all_violations if "stateless-class" in v.rule_id]
149
150
 
150
151
 
@@ -155,7 +156,7 @@ def _execute_stateless_class_lint(params: ExecuteParams) -> NoReturn:
155
156
  params.path_objs, params.config_file, params.verbose, params.project_root
156
157
  )
157
158
  stateless_class_violations = _run_stateless_class_lint(
158
- orchestrator, params.path_objs, params.recursive
159
+ orchestrator, params.path_objs, params.recursive, params.parallel
159
160
  )
160
161
 
161
162
  if params.verbose:
@@ -188,10 +189,10 @@ def _setup_lazy_ignores_orchestrator(
188
189
 
189
190
 
190
191
  def _run_lazy_ignores_lint(
191
- orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool
192
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
192
193
  ) -> list[Violation]:
193
194
  """Execute lazy-ignores lint on files or directories."""
194
- all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive)
195
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
195
196
  return [v for v in all_violations if v.rule_id.startswith("lazy-ignores")]
196
197
 
197
198
 
@@ -202,7 +203,7 @@ def _execute_lazy_ignores_lint(params: ExecuteParams) -> NoReturn:
202
203
  params.path_objs, params.config_file, params.verbose, params.project_root
203
204
  )
204
205
  lazy_ignores_violations = _run_lazy_ignores_lint(
205
- orchestrator, params.path_objs, params.recursive
206
+ orchestrator, params.path_objs, params.recursive, params.parallel
206
207
  )
207
208
 
208
209
  if params.verbose:
@@ -220,3 +221,50 @@ lazy_ignores = create_linter_command(
220
221
  " corresponding entries in the file header's Suppressions section. Enforces a\n"
221
222
  " header-based suppression model requiring human approval for all linting bypasses.",
222
223
  )
224
+
225
+
226
+ # =============================================================================
227
+ # LBYL Command
228
+ # =============================================================================
229
+
230
+
231
+ def _setup_lbyl_orchestrator(
232
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
233
+ ) -> "Orchestrator":
234
+ """Set up orchestrator for lbyl command."""
235
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
236
+
237
+
238
+ def _run_lbyl_lint(
239
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
240
+ ) -> list[Violation]:
241
+ """Execute lbyl lint on files or directories."""
242
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
243
+ return [v for v in all_violations if v.rule_id.startswith("lbyl")]
244
+
245
+
246
+ def _execute_lbyl_lint(params: ExecuteParams) -> NoReturn:
247
+ """Execute lbyl lint."""
248
+ validate_paths_exist(params.path_objs)
249
+ orchestrator = _setup_lbyl_orchestrator(
250
+ params.path_objs, params.config_file, params.verbose, params.project_root
251
+ )
252
+ lbyl_violations = _run_lbyl_lint(
253
+ orchestrator, params.path_objs, params.recursive, params.parallel
254
+ )
255
+
256
+ if params.verbose:
257
+ logger.info(f"Found {len(lbyl_violations)} LBYL violation(s)")
258
+
259
+ format_violations(lbyl_violations, params.format)
260
+ sys.exit(1 if lbyl_violations else 0)
261
+
262
+
263
+ lbyl = create_linter_command(
264
+ "lbyl",
265
+ _execute_lbyl_lint,
266
+ "Check for Look Before You Leap anti-patterns.",
267
+ "Detects LBYL (Look Before You Leap) anti-patterns in Python code that should\n"
268
+ " be refactored to EAFP (Easier to Ask Forgiveness than Permission) style.\n"
269
+ " Examples: 'if key in dict: dict[key]' should use try/except KeyError.",
270
+ )