thailint 0.16.0__tar.gz → 0.17.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 (249) hide show
  1. {thailint-0.16.0 → thailint-0.17.0}/PKG-INFO +5 -2
  2. {thailint-0.16.0 → thailint-0.17.0}/README.md +4 -1
  3. {thailint-0.16.0 → thailint-0.17.0}/pyproject.toml +2 -2
  4. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/__init__.py +8 -1
  5. thailint-0.17.0/src/cli/linters/rust.py +177 -0
  6. {thailint-0.16.0 → thailint-0.17.0}/src/core/base.py +30 -0
  7. {thailint-0.16.0 → thailint-0.17.0}/src/core/constants.py +1 -0
  8. {thailint-0.16.0 → thailint-0.17.0}/src/core/linter_utils.py +42 -1
  9. thailint-0.17.0/src/linters/blocking_async/__init__.py +31 -0
  10. thailint-0.17.0/src/linters/blocking_async/config.py +67 -0
  11. thailint-0.17.0/src/linters/blocking_async/linter.py +183 -0
  12. thailint-0.17.0/src/linters/blocking_async/rust_analyzer.py +419 -0
  13. thailint-0.17.0/src/linters/blocking_async/violation_builder.py +97 -0
  14. thailint-0.17.0/src/linters/clone_abuse/__init__.py +31 -0
  15. thailint-0.17.0/src/linters/clone_abuse/config.py +65 -0
  16. thailint-0.17.0/src/linters/clone_abuse/linter.py +183 -0
  17. thailint-0.17.0/src/linters/clone_abuse/rust_analyzer.py +356 -0
  18. thailint-0.17.0/src/linters/clone_abuse/violation_builder.py +94 -0
  19. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/linter.py +92 -0
  20. thailint-0.17.0/src/linters/magic_numbers/rust_analyzer.py +148 -0
  21. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/violation_builder.py +31 -0
  22. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/linter.py +50 -0
  23. thailint-0.17.0/src/linters/nesting/rust_analyzer.py +118 -0
  24. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/violation_builder.py +32 -0
  25. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/class_analyzer.py +49 -0
  26. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/linter.py +22 -0
  27. thailint-0.17.0/src/linters/srp/rust_analyzer.py +206 -0
  28. thailint-0.17.0/src/linters/unwrap_abuse/__init__.py +30 -0
  29. thailint-0.17.0/src/linters/unwrap_abuse/config.py +59 -0
  30. thailint-0.17.0/src/linters/unwrap_abuse/linter.py +166 -0
  31. thailint-0.17.0/src/linters/unwrap_abuse/rust_analyzer.py +118 -0
  32. thailint-0.17.0/src/linters/unwrap_abuse/violation_builder.py +89 -0
  33. {thailint-0.16.0 → thailint-0.17.0}/src/templates/thailint_config_template.yaml +88 -0
  34. {thailint-0.16.0 → thailint-0.17.0}/CHANGELOG.md +0 -0
  35. {thailint-0.16.0 → thailint-0.17.0}/LICENSE +0 -0
  36. {thailint-0.16.0 → thailint-0.17.0}/src/__init__.py +0 -0
  37. {thailint-0.16.0 → thailint-0.17.0}/src/analyzers/__init__.py +0 -0
  38. {thailint-0.16.0 → thailint-0.17.0}/src/analyzers/ast_utils.py +0 -0
  39. {thailint-0.16.0 → thailint-0.17.0}/src/analyzers/rust_base.py +0 -0
  40. {thailint-0.16.0 → thailint-0.17.0}/src/analyzers/rust_context.py +0 -0
  41. {thailint-0.16.0 → thailint-0.17.0}/src/analyzers/typescript_base.py +0 -0
  42. {thailint-0.16.0 → thailint-0.17.0}/src/api.py +0 -0
  43. {thailint-0.16.0 → thailint-0.17.0}/src/cli/__init__.py +0 -0
  44. {thailint-0.16.0 → thailint-0.17.0}/src/cli/__main__.py +0 -0
  45. {thailint-0.16.0 → thailint-0.17.0}/src/cli/config.py +0 -0
  46. {thailint-0.16.0 → thailint-0.17.0}/src/cli/config_merge.py +0 -0
  47. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/code_patterns.py +0 -0
  48. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/code_smells.py +0 -0
  49. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/documentation.py +0 -0
  50. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/performance.py +0 -0
  51. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/shared.py +0 -0
  52. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/structure.py +0 -0
  53. {thailint-0.16.0 → thailint-0.17.0}/src/cli/linters/structure_quality.py +0 -0
  54. {thailint-0.16.0 → thailint-0.17.0}/src/cli/main.py +0 -0
  55. {thailint-0.16.0 → thailint-0.17.0}/src/cli/utils.py +0 -0
  56. {thailint-0.16.0 → thailint-0.17.0}/src/cli_main.py +0 -0
  57. {thailint-0.16.0 → thailint-0.17.0}/src/config.py +0 -0
  58. {thailint-0.16.0 → thailint-0.17.0}/src/core/__init__.py +0 -0
  59. {thailint-0.16.0 → thailint-0.17.0}/src/core/cli_utils.py +0 -0
  60. {thailint-0.16.0 → thailint-0.17.0}/src/core/config_parser.py +0 -0
  61. {thailint-0.16.0 → thailint-0.17.0}/src/core/python_lint_rule.py +0 -0
  62. {thailint-0.16.0 → thailint-0.17.0}/src/core/registry.py +0 -0
  63. {thailint-0.16.0 → thailint-0.17.0}/src/core/rule_aliases.py +0 -0
  64. {thailint-0.16.0 → thailint-0.17.0}/src/core/rule_discovery.py +0 -0
  65. {thailint-0.16.0 → thailint-0.17.0}/src/core/types.py +0 -0
  66. {thailint-0.16.0 → thailint-0.17.0}/src/core/violation_builder.py +0 -0
  67. {thailint-0.16.0 → thailint-0.17.0}/src/core/violation_utils.py +0 -0
  68. {thailint-0.16.0 → thailint-0.17.0}/src/formatters/__init__.py +0 -0
  69. {thailint-0.16.0 → thailint-0.17.0}/src/formatters/sarif.py +0 -0
  70. {thailint-0.16.0 → thailint-0.17.0}/src/linter_config/__init__.py +0 -0
  71. {thailint-0.16.0 → thailint-0.17.0}/src/linter_config/directive_markers.py +0 -0
  72. {thailint-0.16.0 → thailint-0.17.0}/src/linter_config/ignore.py +0 -0
  73. {thailint-0.16.0 → thailint-0.17.0}/src/linter_config/loader.py +0 -0
  74. {thailint-0.16.0 → thailint-0.17.0}/src/linter_config/pattern_utils.py +0 -0
  75. {thailint-0.16.0 → thailint-0.17.0}/src/linter_config/rule_matcher.py +0 -0
  76. {thailint-0.16.0 → thailint-0.17.0}/src/linters/__init__.py +0 -0
  77. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/__init__.py +0 -0
  78. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/any_all_analyzer.py +0 -0
  79. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/ast_utils.py +0 -0
  80. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/config.py +0 -0
  81. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/continue_analyzer.py +0 -0
  82. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/detector.py +0 -0
  83. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/filter_map_analyzer.py +0 -0
  84. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/linter.py +0 -0
  85. {thailint-0.16.0 → thailint-0.17.0}/src/linters/collection_pipeline/suggestion_builder.py +0 -0
  86. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/__init__.py +0 -0
  87. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/config.py +0 -0
  88. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/function_analyzer.py +0 -0
  89. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/input_detector.py +0 -0
  90. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/linter.py +0 -0
  91. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/output_detector.py +0 -0
  92. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/python_analyzer.py +0 -0
  93. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/types.py +0 -0
  94. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/typescript_cqs_analyzer.py +0 -0
  95. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/typescript_function_analyzer.py +0 -0
  96. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/typescript_input_detector.py +0 -0
  97. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/typescript_output_detector.py +0 -0
  98. {thailint-0.16.0 → thailint-0.17.0}/src/linters/cqs/violation_builder.py +0 -0
  99. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/__init__.py +0 -0
  100. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/base_token_analyzer.py +0 -0
  101. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/block_filter.py +0 -0
  102. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/block_grouper.py +0 -0
  103. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/cache.py +0 -0
  104. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/cache_query.py +0 -0
  105. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/config.py +0 -0
  106. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/config_loader.py +0 -0
  107. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/constant.py +0 -0
  108. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/constant_matcher.py +0 -0
  109. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/constant_violation_builder.py +0 -0
  110. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/deduplicator.py +0 -0
  111. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/duplicate_storage.py +0 -0
  112. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/file_analyzer.py +0 -0
  113. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/inline_ignore.py +0 -0
  114. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/linter.py +0 -0
  115. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/python_analyzer.py +0 -0
  116. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/python_constant_extractor.py +0 -0
  117. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/single_statement_detector.py +0 -0
  118. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/storage_initializer.py +0 -0
  119. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/token_hasher.py +0 -0
  120. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/typescript_analyzer.py +0 -0
  121. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/typescript_constant_extractor.py +0 -0
  122. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/typescript_statement_detector.py +0 -0
  123. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/typescript_value_extractor.py +0 -0
  124. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/violation_builder.py +0 -0
  125. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/violation_filter.py +0 -0
  126. {thailint-0.16.0 → thailint-0.17.0}/src/linters/dry/violation_generator.py +0 -0
  127. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/__init__.py +0 -0
  128. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/atemporal_detector.py +0 -0
  129. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/base_parser.py +0 -0
  130. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/bash_parser.py +0 -0
  131. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/config.py +0 -0
  132. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/css_parser.py +0 -0
  133. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/field_validator.py +0 -0
  134. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/linter.py +0 -0
  135. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/markdown_parser.py +0 -0
  136. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/python_parser.py +0 -0
  137. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/typescript_parser.py +0 -0
  138. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_header/violation_builder.py +0 -0
  139. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/__init__.py +0 -0
  140. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/config_loader.py +0 -0
  141. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/directory_matcher.py +0 -0
  142. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/linter.py +0 -0
  143. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/path_resolver.py +0 -0
  144. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/pattern_matcher.py +0 -0
  145. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/pattern_validator.py +0 -0
  146. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/rule_checker.py +0 -0
  147. {thailint-0.16.0 → thailint-0.17.0}/src/linters/file_placement/violation_factory.py +0 -0
  148. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/__init__.py +0 -0
  149. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/config.py +0 -0
  150. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/directive_utils.py +0 -0
  151. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/header_parser.py +0 -0
  152. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/linter.py +0 -0
  153. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/matcher.py +0 -0
  154. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/python_analyzer.py +0 -0
  155. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/rule_id_utils.py +0 -0
  156. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/skip_detector.py +0 -0
  157. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/types.py +0 -0
  158. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/typescript_analyzer.py +0 -0
  159. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lazy_ignores/violation_builder.py +0 -0
  160. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/__init__.py +0 -0
  161. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/config.py +0 -0
  162. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/linter.py +0 -0
  163. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/__init__.py +0 -0
  164. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/base.py +0 -0
  165. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/dict_key_detector.py +0 -0
  166. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/division_check_detector.py +0 -0
  167. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/file_exists_detector.py +0 -0
  168. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/hasattr_detector.py +0 -0
  169. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/isinstance_detector.py +0 -0
  170. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/len_check_detector.py +0 -0
  171. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/none_check_detector.py +0 -0
  172. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/pattern_detectors/string_validator_detector.py +0 -0
  173. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/python_analyzer.py +0 -0
  174. {thailint-0.16.0 → thailint-0.17.0}/src/linters/lbyl/violation_builder.py +0 -0
  175. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/__init__.py +0 -0
  176. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/config.py +0 -0
  177. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
  178. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/definition_detector.py +0 -0
  179. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
  180. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
  181. {thailint-0.16.0 → thailint-0.17.0}/src/linters/magic_numbers/typescript_ignore_checker.py +0 -0
  182. {thailint-0.16.0 → thailint-0.17.0}/src/linters/method_property/__init__.py +0 -0
  183. {thailint-0.16.0 → thailint-0.17.0}/src/linters/method_property/config.py +0 -0
  184. {thailint-0.16.0 → thailint-0.17.0}/src/linters/method_property/linter.py +0 -0
  185. {thailint-0.16.0 → thailint-0.17.0}/src/linters/method_property/python_analyzer.py +0 -0
  186. {thailint-0.16.0 → thailint-0.17.0}/src/linters/method_property/violation_builder.py +0 -0
  187. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/__init__.py +0 -0
  188. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/config.py +0 -0
  189. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/python_analyzer.py +0 -0
  190. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/typescript_analyzer.py +0 -0
  191. {thailint-0.16.0 → thailint-0.17.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
  192. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/__init__.py +0 -0
  193. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/config.py +0 -0
  194. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/constants.py +0 -0
  195. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/linter.py +0 -0
  196. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/python_analyzer.py +0 -0
  197. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/regex_analyzer.py +0 -0
  198. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/regex_linter.py +0 -0
  199. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/typescript_analyzer.py +0 -0
  200. {thailint-0.16.0 → thailint-0.17.0}/src/linters/performance/violation_builder.py +0 -0
  201. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/__init__.py +0 -0
  202. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/conditional_verbose_analyzer.py +0 -0
  203. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/conditional_verbose_rule.py +0 -0
  204. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/config.py +0 -0
  205. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/linter.py +0 -0
  206. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/python_analyzer.py +0 -0
  207. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/typescript_analyzer.py +0 -0
  208. {thailint-0.16.0 → thailint-0.17.0}/src/linters/print_statements/violation_builder.py +0 -0
  209. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/__init__.py +0 -0
  210. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/config.py +0 -0
  211. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/heuristics.py +0 -0
  212. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/metrics_evaluator.py +0 -0
  213. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/python_analyzer.py +0 -0
  214. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/typescript_analyzer.py +0 -0
  215. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
  216. {thailint-0.16.0 → thailint-0.17.0}/src/linters/srp/violation_builder.py +0 -0
  217. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stateless_class/__init__.py +0 -0
  218. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stateless_class/config.py +0 -0
  219. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stateless_class/linter.py +0 -0
  220. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stateless_class/python_analyzer.py +0 -0
  221. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/__init__.py +0 -0
  222. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/config.py +0 -0
  223. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/context_filter.py +0 -0
  224. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/function_call_violation_builder.py +0 -0
  225. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/ignore_checker.py +0 -0
  226. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/ignore_utils.py +0 -0
  227. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/linter.py +0 -0
  228. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/__init__.py +0 -0
  229. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/analyzer.py +0 -0
  230. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/call_tracker.py +0 -0
  231. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/comparison_tracker.py +0 -0
  232. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/condition_extractor.py +0 -0
  233. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/conditional_detector.py +0 -0
  234. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/constants.py +0 -0
  235. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/match_analyzer.py +0 -0
  236. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/validation_detector.py +0 -0
  237. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/python/variable_extractor.py +0 -0
  238. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/storage.py +0 -0
  239. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/storage_initializer.py +0 -0
  240. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/typescript/__init__.py +0 -0
  241. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/typescript/analyzer.py +0 -0
  242. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/typescript/call_tracker.py +0 -0
  243. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/typescript/comparison_tracker.py +0 -0
  244. {thailint-0.16.0 → thailint-0.17.0}/src/linters/stringly_typed/violation_generator.py +0 -0
  245. {thailint-0.16.0 → thailint-0.17.0}/src/orchestrator/__init__.py +0 -0
  246. {thailint-0.16.0 → thailint-0.17.0}/src/orchestrator/core.py +0 -0
  247. {thailint-0.16.0 → thailint-0.17.0}/src/orchestrator/language_detector.py +0 -0
  248. {thailint-0.16.0 → thailint-0.17.0}/src/utils/__init__.py +0 -0
  249. {thailint-0.16.0 → thailint-0.17.0}/src/utils/project_root.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thailint
3
- Version: 0.16.0
3
+ Version: 0.17.0
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
@@ -44,7 +44,7 @@ Description-Content-Type: text/markdown
44
44
 
45
45
  **The AI Linter** - Catch the mistakes AI coding assistants keep making.
46
46
 
47
- thailint detects anti-patterns that AI tools frequently introduce: duplicate code, excessive nesting, magic numbers, SRP violations, and more. It works across Python, TypeScript, and JavaScript with unified rules - filling gaps that existing linters miss.
47
+ thailint detects anti-patterns that AI tools frequently introduce: duplicate code, excessive nesting, magic numbers, SRP violations, and more. It works across Python, TypeScript, JavaScript, and Rust with unified rules - filling gaps that existing linters miss.
48
48
 
49
49
  ## Installation
50
50
 
@@ -87,6 +87,9 @@ That's it. See violations, fix them, ship better code.
87
87
  | **Improper Logging** | Print statements and conditional verbose patterns | `thailint improper-logging src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/improper-logging-linter/) |
88
88
  | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
89
89
  | **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
90
+ | **Unwrap Abuse** | `.unwrap()`/`.expect()` that panic at runtime (Rust) | `thailint unwrap-abuse src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/unwrap-abuse-linter/) |
91
+ | **Clone Abuse** | `.clone()` abuse: loops, chains, unnecessary (Rust) | `thailint clone-abuse src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/clone-abuse-linter/) |
92
+ | **Blocking Async** | Blocking std:: calls in async functions (Rust) | `thailint blocking-async src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/blocking-async-linter/) |
90
93
 
91
94
  ## Configuration
92
95
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  **The AI Linter** - Catch the mistakes AI coding assistants keep making.
9
9
 
10
- thailint detects anti-patterns that AI tools frequently introduce: duplicate code, excessive nesting, magic numbers, SRP violations, and more. It works across Python, TypeScript, and JavaScript with unified rules - filling gaps that existing linters miss.
10
+ thailint detects anti-patterns that AI tools frequently introduce: duplicate code, excessive nesting, magic numbers, SRP violations, and more. It works across Python, TypeScript, JavaScript, and Rust with unified rules - filling gaps that existing linters miss.
11
11
 
12
12
  ## Installation
13
13
 
@@ -50,6 +50,9 @@ That's it. See violations, fix them, ship better code.
50
50
  | **Improper Logging** | Print statements and conditional verbose patterns | `thailint improper-logging src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/improper-logging-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
52
  | **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
53
+ | **Unwrap Abuse** | `.unwrap()`/`.expect()` that panic at runtime (Rust) | `thailint unwrap-abuse src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/unwrap-abuse-linter/) |
54
+ | **Clone Abuse** | `.clone()` abuse: loops, chains, unnecessary (Rust) | `thailint clone-abuse src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/clone-abuse-linter/) |
55
+ | **Blocking Async** | Blocking std:: calls in async functions (Rust) | `thailint blocking-async src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/blocking-async-linter/) |
53
56
 
54
57
  ## Configuration
55
58
 
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
17
17
 
18
18
  [tool.poetry]
19
19
  name = "thailint"
20
- version = "0.16.0"
20
+ version = "0.17.0"
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"
@@ -232,7 +232,7 @@ disable = [
232
232
 
233
233
  [tool.pylint.format]
234
234
  max-line-length = 120
235
- max-module-lines = 500
235
+ max-module-lines = 600
236
236
 
237
237
  # Flake8 configuration (in .flake8 file, not pyproject.toml)
238
238
  # Note: Flake8 doesn't support pyproject.toml natively
@@ -1,7 +1,8 @@
1
1
  """
2
2
  Purpose: CLI linters package that registers all linter commands to the main CLI group
3
3
 
4
- Scope: Export and registration of all linter CLI commands (nesting, srp, dry, magic-numbers, etc.)
4
+ Scope: Export and registration of all linter CLI commands (nesting, srp, dry, magic-numbers,
5
+ unwrap-abuse, clone-abuse, blocking-async, etc.)
5
6
 
6
7
  Overview: Package initialization that imports all linter command modules to trigger their registration
7
8
  with the main CLI group via Click decorators. Each submodule defines commands using @cli.command()
@@ -29,6 +30,7 @@ from src.cli.linters import ( # noqa: F401
29
30
  code_smells,
30
31
  documentation,
31
32
  performance,
33
+ rust,
32
34
  structure,
33
35
  structure_quality,
34
36
  )
@@ -43,6 +45,7 @@ from src.cli.linters.code_patterns import (
43
45
  from src.cli.linters.code_smells import dry, magic_numbers
44
46
  from src.cli.linters.documentation import file_header
45
47
  from src.cli.linters.performance import perf, regex_in_loop, string_concat_loop
48
+ from src.cli.linters.rust import blocking_async, clone_abuse, unwrap_abuse
46
49
  from src.cli.linters.structure import file_placement, pipeline
47
50
  from src.cli.linters.structure_quality import nesting, srp
48
51
 
@@ -67,4 +70,8 @@ __all__ = [
67
70
  "perf",
68
71
  "string_concat_loop",
69
72
  "regex_in_loop",
73
+ # Rust commands
74
+ "unwrap_abuse",
75
+ "clone_abuse",
76
+ "blocking_async",
70
77
  ]
@@ -0,0 +1,177 @@
1
+ """
2
+ Purpose: CLI commands for Rust-specific linters (unwrap-abuse, clone-abuse, blocking-async)
3
+
4
+ Scope: Commands that detect Rust-specific anti-patterns and code smells
5
+
6
+ Overview: Provides CLI commands for Rust code linting: unwrap-abuse detects .unwrap() and
7
+ .expect() calls that may panic at runtime and suggests safer alternatives; clone-abuse
8
+ detects .clone() abuse patterns including clone in loops, chained clones, and unnecessary
9
+ clones; blocking-async detects blocking operations (std::fs, std::thread::sleep, std::net)
10
+ inside async functions and suggests tokio equivalents. Each command supports standard
11
+ options (config, format, recursive) and integrates with the orchestrator for execution.
12
+
13
+ Dependencies: click for CLI framework, src.cli.main for CLI group, src.cli.utils for shared utilities
14
+
15
+ Exports: unwrap_abuse command, clone_abuse command, blocking_async command
16
+
17
+ Interfaces: Click CLI commands registered to main CLI group
18
+
19
+ Implementation: Click decorators for command definition, orchestrator-based linting execution
20
+ """
21
+
22
+ import sys
23
+ from pathlib import Path
24
+ from typing import TYPE_CHECKING, NoReturn
25
+
26
+ from loguru import logger
27
+
28
+ from src.cli.linters.shared import ExecuteParams, create_linter_command
29
+ from src.cli.utils import execute_linting_on_paths, setup_base_orchestrator, validate_paths_exist
30
+ from src.core.cli_utils import format_violations
31
+ from src.core.types import Violation
32
+
33
+ if TYPE_CHECKING:
34
+ from src.orchestrator.core import Orchestrator
35
+
36
+
37
+ # =============================================================================
38
+ # Unwrap Abuse Command
39
+ # =============================================================================
40
+
41
+
42
+ def _setup_unwrap_abuse_orchestrator(
43
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
44
+ ) -> "Orchestrator":
45
+ """Set up orchestrator for unwrap-abuse command."""
46
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
47
+
48
+
49
+ def _run_unwrap_abuse_lint(
50
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
51
+ ) -> list[Violation]:
52
+ """Execute unwrap-abuse lint on files or directories."""
53
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
54
+ return [v for v in all_violations if v.rule_id.startswith("unwrap-abuse")]
55
+
56
+
57
+ def _execute_unwrap_abuse_lint(params: ExecuteParams) -> NoReturn:
58
+ """Execute unwrap-abuse lint."""
59
+ validate_paths_exist(params.path_objs)
60
+ orchestrator = _setup_unwrap_abuse_orchestrator(
61
+ params.path_objs, params.config_file, params.verbose, params.project_root
62
+ )
63
+ unwrap_abuse_violations = _run_unwrap_abuse_lint(
64
+ orchestrator, params.path_objs, params.recursive, params.parallel
65
+ )
66
+
67
+ logger.debug(f"Found {len(unwrap_abuse_violations)} unwrap abuse violation(s)")
68
+
69
+ format_violations(unwrap_abuse_violations, params.format)
70
+ sys.exit(1 if unwrap_abuse_violations else 0)
71
+
72
+
73
+ unwrap_abuse = create_linter_command(
74
+ "unwrap-abuse",
75
+ _execute_unwrap_abuse_lint,
76
+ "Check for .unwrap() and .expect() abuse in Rust code.",
77
+ "Detects .unwrap() and .expect() calls in Rust code that may panic at runtime.\n"
78
+ " Suggests safer alternatives like the ? operator, unwrap_or(),\n"
79
+ " unwrap_or_default(), or match/if-let expressions.\n"
80
+ " Ignores calls in #[test] functions and #[cfg(test)] modules by default.",
81
+ )
82
+
83
+
84
+ # =============================================================================
85
+ # Clone Abuse Command
86
+ # =============================================================================
87
+
88
+
89
+ def _setup_clone_abuse_orchestrator(
90
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
91
+ ) -> "Orchestrator":
92
+ """Set up orchestrator for clone-abuse command."""
93
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
94
+
95
+
96
+ def _run_clone_abuse_lint(
97
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
98
+ ) -> list[Violation]:
99
+ """Execute clone-abuse lint on files or directories."""
100
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
101
+ return [v for v in all_violations if v.rule_id.startswith("clone-abuse")]
102
+
103
+
104
+ def _execute_clone_abuse_lint(params: ExecuteParams) -> NoReturn:
105
+ """Execute clone-abuse lint."""
106
+ validate_paths_exist(params.path_objs)
107
+ orchestrator = _setup_clone_abuse_orchestrator(
108
+ params.path_objs, params.config_file, params.verbose, params.project_root
109
+ )
110
+ clone_abuse_violations = _run_clone_abuse_lint(
111
+ orchestrator, params.path_objs, params.recursive, params.parallel
112
+ )
113
+
114
+ logger.debug(f"Found {len(clone_abuse_violations)} clone abuse violation(s)")
115
+
116
+ format_violations(clone_abuse_violations, params.format)
117
+ sys.exit(1 if clone_abuse_violations else 0)
118
+
119
+
120
+ clone_abuse = create_linter_command(
121
+ "clone-abuse",
122
+ _execute_clone_abuse_lint,
123
+ "Check for .clone() abuse patterns in Rust code.",
124
+ "Detects .clone() abuse patterns in Rust code: clone in loops,\n"
125
+ " chained .clone().clone() calls, and unnecessary clones where\n"
126
+ " the original is not used after cloning.\n"
127
+ " Suggests borrowing, Rc/Arc, or Cow patterns as alternatives.\n"
128
+ " Ignores calls in #[test] functions and #[cfg(test)] modules by default.",
129
+ )
130
+
131
+
132
+ # =============================================================================
133
+ # Blocking Async Command
134
+ # =============================================================================
135
+
136
+
137
+ def _setup_blocking_async_orchestrator(
138
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
139
+ ) -> "Orchestrator":
140
+ """Set up orchestrator for blocking-async command."""
141
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
142
+
143
+
144
+ def _run_blocking_async_lint(
145
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
146
+ ) -> list[Violation]:
147
+ """Execute blocking-async lint on files or directories."""
148
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
149
+ return [v for v in all_violations if v.rule_id.startswith("blocking-async")]
150
+
151
+
152
+ def _execute_blocking_async_lint(params: ExecuteParams) -> NoReturn:
153
+ """Execute blocking-async lint."""
154
+ validate_paths_exist(params.path_objs)
155
+ orchestrator = _setup_blocking_async_orchestrator(
156
+ params.path_objs, params.config_file, params.verbose, params.project_root
157
+ )
158
+ blocking_async_violations = _run_blocking_async_lint(
159
+ orchestrator, params.path_objs, params.recursive, params.parallel
160
+ )
161
+
162
+ logger.debug(f"Found {len(blocking_async_violations)} blocking-async violation(s)")
163
+
164
+ format_violations(blocking_async_violations, params.format)
165
+ sys.exit(1 if blocking_async_violations else 0)
166
+
167
+
168
+ blocking_async = create_linter_command(
169
+ "blocking-async",
170
+ _execute_blocking_async_lint,
171
+ "Check for blocking operations in async Rust functions.",
172
+ "Detects blocking operations inside async functions in Rust code:\n"
173
+ " std::fs I/O, std::thread::sleep, and blocking std::net calls.\n"
174
+ " Suggests async-compatible alternatives like tokio::fs,\n"
175
+ " tokio::time::sleep, and tokio::net equivalents.\n"
176
+ " Ignores calls in #[test] functions and #[cfg(test)] modules by default.",
177
+ )
@@ -177,12 +177,27 @@ class MultiLanguageLintRule(BaseLintRule):
177
177
  if not config.enabled:
178
178
  return []
179
179
 
180
+ return self._dispatch_by_language(context, config)
181
+
182
+ def _dispatch_by_language(self, context: BaseLintContext, config: Any) -> list[Violation]:
183
+ """Dispatch to language-specific check method.
184
+
185
+ Args:
186
+ context: Lint context with language information
187
+ config: Loaded configuration
188
+
189
+ Returns:
190
+ List of violations from language-specific checker
191
+ """
180
192
  if context.language == Language.PYTHON:
181
193
  return self._check_python(context, config)
182
194
 
183
195
  if context.language in (Language.TYPESCRIPT, Language.JAVASCRIPT):
184
196
  return self._check_typescript(context, config)
185
197
 
198
+ if context.language == Language.RUST:
199
+ return self._check_rust(context, config)
200
+
186
201
  return []
187
202
 
188
203
  @abstractmethod
@@ -222,3 +237,18 @@ class MultiLanguageLintRule(BaseLintRule):
222
237
  List of violations found in TypeScript/JavaScript code
223
238
  """
224
239
  raise NotImplementedError("Subclasses must implement _check_typescript")
240
+
241
+ def _check_rust(self, context: BaseLintContext, config: Any) -> list[Violation]:
242
+ """Check Rust code for violations.
243
+
244
+ Override in subclasses to add Rust language support.
245
+ Returns empty list by default for linters that do not support Rust.
246
+
247
+ Args:
248
+ context: Lint context with Rust file information
249
+ config: Loaded configuration
250
+
251
+ Returns:
252
+ List of violations found in Rust code
253
+ """
254
+ return []
@@ -28,6 +28,7 @@ class Language(str, Enum):
28
28
  TYPESCRIPT = "typescript"
29
29
  JAVASCRIPT = "javascript"
30
30
  MARKDOWN = "markdown"
31
+ RUST = "rust"
31
32
 
32
33
 
33
34
  class StorageMode(str, Enum):
@@ -13,7 +13,7 @@ Overview: Provides reusable helper functions to eliminate duplication across lin
13
13
  Dependencies: BaseLintContext from src.core.base, ast for Python parsing
14
14
 
15
15
  Exports: get_metadata, get_metadata_value, load_linter_config, has_file_content, parse_python_ast,
16
- with_parsed_python
16
+ with_parsed_python, resolve_file_path, is_ignored_path, get_line_context
17
17
 
18
18
  Interfaces: All functions take BaseLintContext and return typed values (dict, str, bool, Any)
19
19
 
@@ -255,3 +255,44 @@ def with_parsed_python(
255
255
  # tree is guaranteed non-None when errors is empty (parse_python_ast contract)
256
256
  assert tree is not None # nosec B101
257
257
  return on_success(tree)
258
+
259
+
260
+ def resolve_file_path(context: BaseLintContext) -> str:
261
+ """Resolve file path from context.
262
+
263
+ Args:
264
+ context: Lint context
265
+
266
+ Returns:
267
+ File path string, or "unknown" if not available
268
+ """
269
+ return str(context.file_path) if context.file_path else "unknown"
270
+
271
+
272
+ def is_ignored_path(file_path: str, ignore_patterns: list[str]) -> bool:
273
+ """Check if file path matches any ignore pattern.
274
+
275
+ Args:
276
+ file_path: Path to check
277
+ ignore_patterns: List of patterns to match against
278
+
279
+ Returns:
280
+ True if the path should be ignored
281
+ """
282
+ return any(ignored in file_path for ignored in ignore_patterns)
283
+
284
+
285
+ def get_line_context(code: str, line_index: int) -> str:
286
+ """Get the code line at the given index for context.
287
+
288
+ Args:
289
+ code: Full source code
290
+ line_index: Zero-indexed line number
291
+
292
+ Returns:
293
+ Stripped line content, or empty string if index out of range
294
+ """
295
+ lines = code.split("\n")
296
+ if 0 <= line_index < len(lines):
297
+ return lines[line_index].strip()
298
+ return ""
@@ -0,0 +1,31 @@
1
+ """
2
+ Purpose: Rust blocking-in-async detector package exports
3
+
4
+ Scope: Detect blocking operations inside async functions in Rust code and suggest alternatives
5
+
6
+ Overview: Package providing blocking-in-async detection for Rust code. Identifies std::fs I/O
7
+ operations, std::thread::sleep calls, and blocking std::net operations inside async functions.
8
+ Suggests async-compatible alternatives including tokio::fs, tokio::time::sleep, and tokio::net
9
+ equivalents. Supports configuration for allowing calls in test code, toggling individual
10
+ pattern detection, and ignoring specific directories. Uses tree-sitter for accurate
11
+ AST-based detection of async function contexts.
12
+
13
+ Dependencies: tree-sitter-rust (optional) for AST parsing, src.core for base classes
14
+
15
+ Exports: BlockingAsyncConfig, BlockingAsyncRule, RustBlockingAsyncAnalyzer, BlockingCall
16
+
17
+ Interfaces: BlockingAsyncConfig.from_dict() for YAML configuration loading
18
+
19
+ Implementation: Tree-sitter AST-based async context detection with blocking API pattern matching
20
+ """
21
+
22
+ from .config import BlockingAsyncConfig
23
+ from .linter import BlockingAsyncRule
24
+ from .rust_analyzer import BlockingCall, RustBlockingAsyncAnalyzer
25
+
26
+ __all__ = [
27
+ "BlockingAsyncConfig",
28
+ "BlockingAsyncRule",
29
+ "RustBlockingAsyncAnalyzer",
30
+ "BlockingCall",
31
+ ]
@@ -0,0 +1,67 @@
1
+ """
2
+ Purpose: Configuration dataclass for Rust blocking-in-async detector
3
+
4
+ Scope: Pattern toggles, ignore patterns, and configuration for blocking-in-async detection
5
+
6
+ Overview: Provides BlockingAsyncConfig dataclass with toggles for controlling detection of
7
+ blocking operations inside async functions in Rust code. Supports toggling detection of
8
+ std::fs operations, std::thread::sleep, and blocking network calls independently. Includes
9
+ configuration for allowing calls in test code, ignoring example and benchmark directories.
10
+ Configuration loads from YAML with sensible defaults via from_dict() class method.
11
+
12
+ Dependencies: dataclasses, typing
13
+
14
+ Exports: BlockingAsyncConfig
15
+
16
+ Interfaces: BlockingAsyncConfig.from_dict() for YAML configuration loading
17
+
18
+ Implementation: Dataclass with factory defaults and conservative default settings
19
+ """
20
+
21
+ from dataclasses import dataclass, field
22
+ from typing import Any
23
+
24
+
25
+ @dataclass
26
+ class BlockingAsyncConfig:
27
+ """Configuration for blocking-in-async detection."""
28
+
29
+ enabled: bool = True
30
+
31
+ # Allow blocking calls in test functions and #[cfg(test)] modules
32
+ allow_in_tests: bool = True
33
+
34
+ # Toggle detection of std::fs operations in async functions
35
+ detect_fs_in_async: bool = True
36
+
37
+ # Toggle detection of std::thread::sleep in async functions
38
+ detect_sleep_in_async: bool = True
39
+
40
+ # Toggle detection of std::net blocking calls in async functions
41
+ detect_net_in_async: bool = True
42
+
43
+ # File path patterns to ignore (e.g., examples/, benches/)
44
+ ignore: list[str] = field(default_factory=lambda: ["examples/", "benches/", "tests/"])
45
+
46
+ @classmethod
47
+ def from_dict(
48
+ cls, config: dict[str, Any], language: str | None = None
49
+ ) -> "BlockingAsyncConfig":
50
+ """Load configuration from dictionary.
51
+
52
+ Args:
53
+ config: Configuration dictionary from YAML
54
+ language: Language parameter (reserved for future use)
55
+
56
+ Returns:
57
+ Configured BlockingAsyncConfig instance
58
+ """
59
+ _ = language
60
+ return cls(
61
+ enabled=config.get("enabled", True),
62
+ allow_in_tests=config.get("allow_in_tests", True),
63
+ detect_fs_in_async=config.get("detect_fs_in_async", True),
64
+ detect_sleep_in_async=config.get("detect_sleep_in_async", True),
65
+ detect_net_in_async=config.get("detect_net_in_async", True),
66
+ ignore=config.get("ignore", ["examples/", "benches/", "tests/"]),
67
+ )
@@ -0,0 +1,183 @@
1
+ """
2
+ Purpose: Main linter rule for detecting blocking operations in async Rust code
3
+
4
+ Scope: Entry point for blocking-in-async detection implementing BaseLintRule interface
5
+
6
+ Overview: Provides BlockingAsyncRule class that implements the BaseLintRule interface for
7
+ detecting blocking API calls inside async functions in Rust code. Validates that files
8
+ are Rust with content, loads configuration, checks ignored paths, and delegates analysis
9
+ to RustBlockingAsyncAnalyzer. Filters detected calls based on configuration (allow_in_tests,
10
+ pattern toggles, ignored paths) and converts remaining calls to Violation objects via the
11
+ violation builder. Supports disabling via configuration.
12
+
13
+ Dependencies: BaseLintRule, RustBlockingAsyncAnalyzer, BlockingAsyncConfig, violation_builder
14
+
15
+ Exports: BlockingAsyncRule
16
+
17
+ Interfaces: check(context: BaseLintContext) -> list[Violation]
18
+
19
+ Implementation: Single-file analysis with config-driven filtering and tree-sitter-based detection
20
+ """
21
+
22
+ from src.core.base import BaseLintContext, BaseLintRule
23
+ from src.core.linter_utils import (
24
+ has_file_content,
25
+ is_ignored_path,
26
+ load_linter_config,
27
+ resolve_file_path,
28
+ )
29
+ from src.core.types import Violation
30
+
31
+ from .config import BlockingAsyncConfig
32
+ from .rust_analyzer import BlockingCall, RustBlockingAsyncAnalyzer
33
+ from .violation_builder import (
34
+ build_fs_in_async_violation,
35
+ build_net_in_async_violation,
36
+ build_sleep_in_async_violation,
37
+ )
38
+
39
+ _PATTERN_BUILDERS = {
40
+ "fs-in-async": build_fs_in_async_violation,
41
+ "sleep-in-async": build_sleep_in_async_violation,
42
+ "net-in-async": build_net_in_async_violation,
43
+ }
44
+
45
+ _PATTERN_CONFIG_KEYS = {
46
+ "fs-in-async": "detect_fs_in_async",
47
+ "sleep-in-async": "detect_sleep_in_async",
48
+ "net-in-async": "detect_net_in_async",
49
+ }
50
+
51
+
52
+ class BlockingAsyncRule(BaseLintRule):
53
+ """Detects blocking operations inside async functions in Rust code."""
54
+
55
+ def __init__(self, config: BlockingAsyncConfig | None = None) -> None:
56
+ """Initialize blocking-in-async rule.
57
+
58
+ Args:
59
+ config: Optional configuration override for testing
60
+ """
61
+ self._config_override = config
62
+ self._analyzer = RustBlockingAsyncAnalyzer()
63
+
64
+ @property
65
+ def rule_id(self) -> str:
66
+ """Unique identifier for this rule."""
67
+ return "blocking-async"
68
+
69
+ @property
70
+ def rule_name(self) -> str:
71
+ """Human-readable name for this rule."""
72
+ return "Rust Blocking in Async"
73
+
74
+ @property
75
+ def description(self) -> str:
76
+ """Description of what this rule checks."""
77
+ return (
78
+ "Detects blocking operations inside async functions in Rust code including "
79
+ "std::fs I/O, std::thread::sleep, and blocking std::net calls. Suggests "
80
+ "async-compatible alternatives like tokio::fs, tokio::time::sleep, and tokio::net."
81
+ )
82
+
83
+ def check(self, context: BaseLintContext) -> list[Violation]:
84
+ """Check for blocking-in-async violations in a Rust file.
85
+
86
+ Args:
87
+ context: Lint context with file content and metadata
88
+
89
+ Returns:
90
+ List of violations found
91
+ """
92
+ config = self._get_config(context)
93
+ if not self._should_analyze(context, config):
94
+ return []
95
+
96
+ file_path = resolve_file_path(context)
97
+ calls = self._analyzer.find_blocking_calls(context.file_content or "")
98
+ return self._build_violations(calls, config, file_path)
99
+
100
+ def _should_analyze(self, context: BaseLintContext, config: BlockingAsyncConfig) -> bool:
101
+ """Determine if the file should be analyzed.
102
+
103
+ Args:
104
+ context: Lint context to check
105
+ config: Blocking-in-async configuration
106
+
107
+ Returns:
108
+ True if the file should be analyzed
109
+ """
110
+ if context.language != "rust":
111
+ return False
112
+ if not has_file_content(context):
113
+ return False
114
+ if not config.enabled:
115
+ return False
116
+ return not is_ignored_path(resolve_file_path(context), config.ignore)
117
+
118
+ def _get_config(self, context: BaseLintContext) -> BlockingAsyncConfig:
119
+ """Load configuration from override or context metadata.
120
+
121
+ Args:
122
+ context: Lint context with metadata
123
+
124
+ Returns:
125
+ BlockingAsyncConfig instance
126
+ """
127
+ if self._config_override is not None:
128
+ return self._config_override
129
+ return load_linter_config(context, "blocking-async", BlockingAsyncConfig)
130
+
131
+ def _build_violations(
132
+ self,
133
+ calls: list[BlockingCall],
134
+ config: BlockingAsyncConfig,
135
+ file_path: str,
136
+ ) -> list[Violation]:
137
+ """Convert blocking calls to violations with config filtering.
138
+
139
+ Args:
140
+ calls: Detected blocking calls from analyzer
141
+ config: Configuration for filtering
142
+ file_path: Path of the analyzed file
143
+
144
+ Returns:
145
+ List of filtered violations
146
+ """
147
+ return [
148
+ _build_violation_for_call(call, file_path)
149
+ for call in calls
150
+ if not _should_skip_call(call, config)
151
+ ]
152
+
153
+
154
+ def _should_skip_call(call: BlockingCall, config: BlockingAsyncConfig) -> bool:
155
+ """Determine if a blocking call should be skipped based on config.
156
+
157
+ Args:
158
+ call: Detected blocking call
159
+ config: Configuration for filtering
160
+
161
+ Returns:
162
+ True if the call should be skipped
163
+ """
164
+ if call.is_in_test and config.allow_in_tests:
165
+ return True
166
+ config_key = _PATTERN_CONFIG_KEYS.get(call.pattern)
167
+ if config_key and not getattr(config, config_key):
168
+ return True
169
+ return False
170
+
171
+
172
+ def _build_violation_for_call(call: BlockingCall, file_path: str) -> Violation:
173
+ """Build a violation for a specific blocking call.
174
+
175
+ Args:
176
+ call: Detected blocking call with pattern info
177
+ file_path: Path of the analyzed file
178
+
179
+ Returns:
180
+ Violation instance
181
+ """
182
+ builder = _PATTERN_BUILDERS.get(call.pattern, build_fs_in_async_violation)
183
+ return builder(file_path, call.line, call.column, call.context)