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.
- {thailint-0.15.0 → thailint-0.15.2}/PKG-INFO +6 -4
- {thailint-0.15.0 → thailint-0.15.2}/README.md +3 -2
- {thailint-0.15.0 → thailint-0.15.2}/pyproject.toml +3 -1
- thailint-0.15.2/src/analyzers/rust_base.py +155 -0
- thailint-0.15.2/src/analyzers/rust_context.py +141 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/config.py +6 -4
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/code_patterns.py +64 -16
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/code_smells.py +23 -14
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/documentation.py +5 -3
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/performance.py +23 -10
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/shared.py +22 -6
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/structure.py +13 -4
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/structure_quality.py +9 -4
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/utils.py +4 -4
- {thailint-0.15.0 → thailint-0.15.2}/src/config.py +34 -21
- thailint-0.15.2/src/core/python_lint_rule.py +101 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/ignore.py +2 -1
- thailint-0.15.2/src/linters/cqs/__init__.py +54 -0
- thailint-0.15.2/src/linters/cqs/config.py +55 -0
- thailint-0.15.2/src/linters/cqs/function_analyzer.py +201 -0
- thailint-0.15.2/src/linters/cqs/input_detector.py +139 -0
- thailint-0.15.2/src/linters/cqs/linter.py +159 -0
- thailint-0.15.2/src/linters/cqs/output_detector.py +84 -0
- thailint-0.15.2/src/linters/cqs/python_analyzer.py +54 -0
- thailint-0.15.2/src/linters/cqs/types.py +82 -0
- thailint-0.15.2/src/linters/cqs/typescript_cqs_analyzer.py +61 -0
- thailint-0.15.2/src/linters/cqs/typescript_function_analyzer.py +192 -0
- thailint-0.15.2/src/linters/cqs/typescript_input_detector.py +203 -0
- thailint-0.15.2/src/linters/cqs/typescript_output_detector.py +117 -0
- thailint-0.15.2/src/linters/cqs/violation_builder.py +94 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_value_extractor.py +2 -1
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/linter.py +2 -1
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/linter.py +6 -6
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/pattern_validator.py +6 -5
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/rule_checker.py +10 -5
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/config.py +5 -3
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/python_analyzer.py +5 -1
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/types.py +2 -1
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lbyl/__init__.py +3 -1
- thailint-0.15.2/src/linters/lbyl/linter.py +67 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/__init__.py +53 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lbyl/pattern_detectors/base.py +24 -7
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
- thailint-0.15.2/src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
- thailint-0.15.2/src/linters/lbyl/python_analyzer.py +215 -0
- thailint-0.15.2/src/linters/lbyl/violation_builder.py +354 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/heuristics.py +47 -14
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/typescript_metrics_calculator.py +34 -10
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/ignore_checker.py +4 -6
- {thailint-0.15.0 → thailint-0.15.2}/src/orchestrator/language_detector.py +5 -3
- thailint-0.15.0/src/linters/lbyl/pattern_detectors/__init__.py +0 -25
- {thailint-0.15.0 → thailint-0.15.2}/CHANGELOG.md +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/LICENSE +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/analyzers/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/analyzers/ast_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/api.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/__main__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/config_merge.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/linters/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli/main.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/cli_main.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/base.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/cli_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/config_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/constants.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/linter_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/registry.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/rule_discovery.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/types.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/core/violation_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/formatters/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/formatters/sarif.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/directive_markers.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/loader.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/pattern_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linter_config/rule_matcher.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/any_all_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/ast_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/continue_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/filter_map_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/collection_pipeline/suggestion_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/block_filter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/cache.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/constant.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/constant_matcher.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/constant_violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/duplicate_storage.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/file_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/python_constant_extractor.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/single_statement_detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/token_hasher.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_constant_extractor.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/typescript_statement_detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/violation_filter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/dry/violation_generator.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/atemporal_detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/base_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/bash_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/css_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/field_validator.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/markdown_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/python_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/typescript_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_header/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/pattern_matcher.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/directive_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/header_parser.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/matcher.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/rule_id_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/skip_detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lazy_ignores/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/lbyl/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/context_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/typescript_ignore_checker.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/method_property/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/constants.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/regex_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/regex_linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/performance/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/print_statements/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stateless_class/python_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/config.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/context_filter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/function_call_violation_builder.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/ignore_utils.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/linter.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/call_tracker.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/comparison_tracker.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/condition_extractor.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/conditional_detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/constants.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/match_analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/validation_detector.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/python/variable_extractor.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/storage.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/storage_initializer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/analyzer.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/call_tracker.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/typescript/comparison_tracker.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/linters/stringly_typed/violation_generator.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/orchestrator/__init__.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/orchestrator/core.py +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/templates/thailint_config_template.yaml +0 -0
- {thailint-0.15.0 → thailint-0.15.2}/src/utils/__init__.py +0 -0
- {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.
|
|
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
|
[](https://opensource.org/licenses/MIT)
|
|
39
40
|
[](https://www.python.org/downloads/)
|
|
40
|
-
[](https://pypi.org/project/thailint/)
|
|
41
42
|
[](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
|
|
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
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
-
[](https://pypi.org/project/thailint/)
|
|
6
6
|
[](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
|
|
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.
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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,
|
|
10
|
-
standard options (config, format, recursive) and
|
|
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
|
|
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
|
+
)
|