thailint 0.14.0__tar.gz → 0.16.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.
- {thailint-0.14.0 → thailint-0.16.0}/CHANGELOG.md +26 -0
- {thailint-0.14.0 → thailint-0.16.0}/PKG-INFO +12 -7
- {thailint-0.14.0 → thailint-0.16.0}/README.md +8 -5
- {thailint-0.14.0 → thailint-0.16.0}/pyproject.toml +4 -2
- thailint-0.16.0/src/analyzers/rust_base.py +155 -0
- thailint-0.16.0/src/analyzers/rust_context.py +141 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/config.py +10 -16
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/__init__.py +5 -2
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/code_patterns.py +98 -46
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/code_smells.py +29 -29
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/documentation.py +8 -9
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/performance.py +27 -20
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/shared.py +25 -14
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/structure.py +17 -15
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/linters/structure_quality.py +13 -15
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/main.py +9 -12
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/utils.py +11 -20
- {thailint-0.14.0 → thailint-0.16.0}/src/config.py +34 -21
- {thailint-0.14.0 → thailint-0.16.0}/src/core/__init__.py +14 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/cli_utils.py +24 -4
- thailint-0.16.0/src/core/python_lint_rule.py +101 -0
- thailint-0.16.0/src/core/rule_aliases.py +84 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linter_config/ignore.py +2 -1
- {thailint-0.14.0 → thailint-0.16.0}/src/linter_config/rule_matcher.py +53 -8
- thailint-0.16.0/src/linters/cqs/__init__.py +54 -0
- thailint-0.16.0/src/linters/cqs/config.py +55 -0
- thailint-0.16.0/src/linters/cqs/function_analyzer.py +201 -0
- thailint-0.16.0/src/linters/cqs/input_detector.py +139 -0
- thailint-0.16.0/src/linters/cqs/linter.py +159 -0
- thailint-0.16.0/src/linters/cqs/output_detector.py +84 -0
- thailint-0.16.0/src/linters/cqs/python_analyzer.py +54 -0
- thailint-0.16.0/src/linters/cqs/types.py +82 -0
- thailint-0.16.0/src/linters/cqs/typescript_cqs_analyzer.py +61 -0
- thailint-0.16.0/src/linters/cqs/typescript_function_analyzer.py +192 -0
- thailint-0.16.0/src/linters/cqs/typescript_input_detector.py +203 -0
- thailint-0.16.0/src/linters/cqs/typescript_output_detector.py +117 -0
- thailint-0.16.0/src/linters/cqs/violation_builder.py +94 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/linter.py +79 -8
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/typescript_value_extractor.py +2 -1
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/violation_generator.py +70 -10
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/linter.py +2 -1
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/linter.py +6 -6
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/pattern_validator.py +6 -5
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/rule_checker.py +10 -5
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/config.py +11 -3
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/directive_utils.py +47 -4
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/matcher.py +42 -9
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/python_analyzer.py +6 -2
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/types.py +3 -1
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/violation_builder.py +5 -1
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lbyl/__init__.py +3 -1
- thailint-0.16.0/src/linters/lbyl/linter.py +67 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/__init__.py +53 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lbyl/pattern_detectors/base.py +24 -7
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
- thailint-0.16.0/src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
- thailint-0.16.0/src/linters/lbyl/python_analyzer.py +215 -0
- thailint-0.16.0/src/linters/lbyl/violation_builder.py +354 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/config.py +38 -7
- thailint-0.16.0/src/linters/magic_numbers/definition_detector.py +226 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/linter.py +7 -0
- thailint-0.16.0/src/linters/nesting/python_analyzer.py +156 -0
- thailint-0.16.0/src/linters/print_statements/__init__.py +65 -0
- thailint-0.16.0/src/linters/print_statements/conditional_verbose_analyzer.py +200 -0
- thailint-0.16.0/src/linters/print_statements/conditional_verbose_rule.py +254 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/print_statements/linter.py +2 -2
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/heuristics.py +47 -14
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/typescript_metrics_calculator.py +34 -10
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stateless_class/config.py +4 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stateless_class/linter.py +116 -4
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stateless_class/python_analyzer.py +86 -4
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/ignore_checker.py +4 -6
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/storage.py +4 -14
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/violation_generator.py +27 -13
- {thailint-0.14.0 → thailint-0.16.0}/src/orchestrator/language_detector.py +5 -3
- thailint-0.14.0/src/linters/lbyl/pattern_detectors/__init__.py +0 -25
- thailint-0.14.0/src/linters/nesting/python_analyzer.py +0 -93
- thailint-0.14.0/src/linters/print_statements/__init__.py +0 -53
- {thailint-0.14.0 → thailint-0.16.0}/LICENSE +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/analyzers/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/analyzers/ast_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/api.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/__main__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/cli/config_merge.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/cli_main.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/base.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/config_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/constants.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/linter_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/registry.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/rule_discovery.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/types.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/core/violation_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/formatters/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/formatters/sarif.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linter_config/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linter_config/directive_markers.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linter_config/loader.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linter_config/pattern_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/any_all_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/ast_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/continue_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/filter_map_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/collection_pipeline/suggestion_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/block_filter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/cache.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/constant.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/constant_matcher.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/constant_violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/duplicate_storage.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/file_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/python_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/python_constant_extractor.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/single_statement_detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/token_hasher.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/typescript_constant_extractor.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/typescript_statement_detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/dry/violation_filter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/atemporal_detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/base_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/bash_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/css_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/field_validator.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/markdown_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/python_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/typescript_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_header/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/pattern_matcher.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/header_parser.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/rule_id_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/skip_detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lazy_ignores/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/lbyl/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/typescript_ignore_checker.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/method_property/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/method_property/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/method_property/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/method_property/python_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/method_property/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/nesting/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/nesting/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/nesting/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/constants.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/python_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/regex_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/regex_linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/performance/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/print_statements/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/print_statements/python_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/print_statements/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/print_statements/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stateless_class/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/config.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/context_filter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/function_call_violation_builder.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/ignore_utils.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/linter.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/call_tracker.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/comparison_tracker.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/condition_extractor.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/conditional_detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/constants.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/match_analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/validation_detector.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/python/variable_extractor.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/storage_initializer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/typescript/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/typescript/analyzer.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/typescript/call_tracker.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/linters/stringly_typed/typescript/comparison_tracker.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/orchestrator/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/orchestrator/core.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/templates/thailint_config_template.yaml +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/utils/__init__.py +0 -0
- {thailint-0.14.0 → thailint-0.16.0}/src/utils/project_root.py +0 -0
|
@@ -27,6 +27,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
27
|
### Added
|
|
28
28
|
|
|
29
29
|
- **Stateless Class Linter** - Detect Python classes without state that should be module-level functions
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- **Parallel Processing** - Added `--parallel` flag documentation to CLI reference and README
|
|
34
|
+
- Documents multi-core processing with ProcessPoolExecutor (up to 8 workers)
|
|
35
|
+
- Explains automatic fallback to sequential for small file counts
|
|
36
|
+
- Provides usage guidance and performance expectations
|
|
37
|
+
|
|
38
|
+
## [0.15.4] - 2026-01-26
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Inline Justifications for Lazy-Ignores** (#147) - Allow inline justifications directly next to ignore statements
|
|
43
|
+
- Syntax: `# noqa: PLR0912 - state machine inherently complex`
|
|
44
|
+
- Delimiter: ` - ` (space-dash-space) followed by justification text
|
|
45
|
+
- Inline justifications take precedence over header-based Suppressions
|
|
46
|
+
- Minimum justification length: 10 characters (configurable)
|
|
47
|
+
- New config options: `allow_inline_justifications`, `min_justification_length`
|
|
48
|
+
- Supports all ignore types: noqa, type:ignore, pylint:disable, nosec, pyright:ignore
|
|
49
|
+
- 27 new tests for inline justification functionality
|
|
50
|
+
|
|
51
|
+
## [0.15.3] - 2026-01-26
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **DRY Linter**: Ignore directives (`# thailint: ignore-start dry` / `ignore-end`) now work correctly for both duplicate code and duplicate constant detection (#144)
|
|
30
56
|
- AST-based detection of classes without `__init__`/`__new__` constructors
|
|
31
57
|
- Detects classes without instance state (`self.attr` assignments)
|
|
32
58
|
- Excludes ABC, Protocol, and decorated classes (legitimate patterns)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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
|
|
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
|
|
@@ -24,9 +24,11 @@ Classifier: Topic :: Software Development :: Testing
|
|
|
24
24
|
Classifier: Topic :: Utilities
|
|
25
25
|
Classifier: Typing :: Typed
|
|
26
26
|
Requires-Dist: click (>=8.1.0,<9.0.0)
|
|
27
|
+
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
|
27
28
|
Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
|
|
28
29
|
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
29
30
|
Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
|
|
31
|
+
Requires-Dist: tree-sitter-rust (>=0.23.2,<0.24.0)
|
|
30
32
|
Requires-Dist: tree-sitter-typescript (>=0.23.2,<0.24.0)
|
|
31
33
|
Project-URL: Documentation, https://thai-lint.readthedocs.io/
|
|
32
34
|
Project-URL: Homepage, https://github.com/be-wise-be-kind/thai-lint
|
|
@@ -37,7 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
37
39
|
|
|
38
40
|
[](https://opensource.org/licenses/MIT)
|
|
39
41
|
[](https://www.python.org/downloads/)
|
|
40
|
-
[](https://pypi.org/project/thailint/)
|
|
41
43
|
[](https://thai-lint.readthedocs.io/)
|
|
42
44
|
|
|
43
45
|
**The AI Linter** - Catch the mistakes AI coding assistants keep making.
|
|
@@ -47,7 +49,7 @@ thailint detects anti-patterns that AI tools frequently introduce: duplicate cod
|
|
|
47
49
|
## Installation
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
|
-
pip install
|
|
52
|
+
pip install thailint
|
|
51
53
|
```
|
|
52
54
|
|
|
53
55
|
Or with Docker:
|
|
@@ -82,8 +84,9 @@ That's it. See violations, fix them, ship better code.
|
|
|
82
84
|
| **Method Property** | Methods that should be @property | `thailint method-property src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/method-property-linter/) |
|
|
83
85
|
| **File Placement** | Files in wrong directories | `thailint file-placement src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/) |
|
|
84
86
|
| **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
|
|
85
|
-
| **
|
|
87
|
+
| **Improper Logging** | Print statements and conditional verbose patterns | `thailint improper-logging src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/improper-logging-linter/) |
|
|
86
88
|
| **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
|
|
89
|
+
| **LBYL** | Look Before You Leap anti-patterns | `thailint lbyl src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lbyl-linter/) |
|
|
87
90
|
|
|
88
91
|
## Configuration
|
|
89
92
|
|
|
@@ -150,10 +153,12 @@ See [How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to
|
|
|
150
153
|
- name: Run thailint
|
|
151
154
|
run: |
|
|
152
155
|
pip install thai-lint
|
|
153
|
-
thailint dry src/
|
|
154
|
-
thailint nesting src/
|
|
156
|
+
thailint --parallel dry src/
|
|
157
|
+
thailint --parallel nesting src/
|
|
155
158
|
```
|
|
156
159
|
|
|
160
|
+
Use `--parallel` for faster linting on large codebases (2-4x speedup on multi-core systems).
|
|
161
|
+
|
|
157
162
|
Exit codes: `0` = success, `1` = violations found, `2` = error.
|
|
158
163
|
|
|
159
164
|
## Documentation
|
|
@@ -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:
|
|
@@ -47,8 +47,9 @@ That's it. See violations, fix them, ship better code.
|
|
|
47
47
|
| **Method Property** | Methods that should be @property | `thailint method-property src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/method-property-linter/) |
|
|
48
48
|
| **File Placement** | Files in wrong directories | `thailint file-placement src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/) |
|
|
49
49
|
| **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
|
|
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
|
+
| **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
|
|
|
@@ -115,10 +116,12 @@ See [How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to
|
|
|
115
116
|
- name: Run thailint
|
|
116
117
|
run: |
|
|
117
118
|
pip install thai-lint
|
|
118
|
-
thailint dry src/
|
|
119
|
-
thailint nesting src/
|
|
119
|
+
thailint --parallel dry src/
|
|
120
|
+
thailint --parallel nesting src/
|
|
120
121
|
```
|
|
121
122
|
|
|
123
|
+
Use `--parallel` for faster linting on large codebases (2-4x speedup on multi-core systems).
|
|
124
|
+
|
|
122
125
|
Exit codes: `0` = success, `1` = violations found, `2` = error.
|
|
123
126
|
|
|
124
127
|
## Documentation
|
|
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
17
17
|
|
|
18
18
|
[tool.poetry]
|
|
19
19
|
name = "thailint"
|
|
20
|
-
version = "0.
|
|
20
|
+
version = "0.16.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"
|
|
@@ -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,7 +76,9 @@ 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"
|
|
81
|
+
loguru = "^0.7.3"
|
|
79
82
|
|
|
80
83
|
[tool.poetry.group.dev.dependencies]
|
|
81
84
|
# Core testing and linting
|
|
@@ -102,7 +105,6 @@ xenon = "^0.9.3"
|
|
|
102
105
|
# Comprehensive security
|
|
103
106
|
safety = "^3.2.11"
|
|
104
107
|
pip-audit = "^2.8.0"
|
|
105
|
-
loguru = "^0.7.3"
|
|
106
108
|
pytest-xdist = "^3.8.0"
|
|
107
109
|
|
|
108
110
|
[tool.poetry.scripts]
|
|
@@ -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)
|
|
@@ -21,22 +21,18 @@ Implementation: Uses Click decorators for command definition, supports multiple
|
|
|
21
21
|
validates configuration changes before saving, uses template file for init-config generation
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
import logging
|
|
25
24
|
import sys
|
|
26
25
|
from pathlib import Path
|
|
27
26
|
|
|
28
27
|
import click
|
|
29
28
|
import yaml
|
|
29
|
+
from loguru import logger
|
|
30
30
|
|
|
31
31
|
from src.config import ConfigError, save_config, validate_config
|
|
32
32
|
|
|
33
33
|
from .config_merge import perform_merge
|
|
34
34
|
from .main import cli
|
|
35
35
|
|
|
36
|
-
# Configure module logger
|
|
37
|
-
logger = logging.getLogger(__name__)
|
|
38
|
-
|
|
39
|
-
|
|
40
36
|
# =============================================================================
|
|
41
37
|
# Config Command Group
|
|
42
38
|
# =============================================================================
|
|
@@ -150,12 +146,14 @@ def config_get(ctx: click.Context, key: str) -> None:
|
|
|
150
146
|
|
|
151
147
|
def _convert_value_type(value: str) -> bool | int | float | str:
|
|
152
148
|
"""Convert string value to appropriate type."""
|
|
149
|
+
from contextlib import suppress
|
|
150
|
+
|
|
153
151
|
if value.lower() in ["true", "false"]:
|
|
154
152
|
return value.lower() == "true"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
# Use EAFP pattern for numeric conversion
|
|
154
|
+
for converter in (int, float):
|
|
155
|
+
with suppress(ValueError):
|
|
156
|
+
return converter(value)
|
|
159
157
|
return value
|
|
160
158
|
|
|
161
159
|
|
|
@@ -175,8 +173,7 @@ def _save_and_report_success(
|
|
|
175
173
|
"""Save configuration and report success."""
|
|
176
174
|
save_config(cfg, config_path)
|
|
177
175
|
click.echo(f"Set {key} = {value}")
|
|
178
|
-
|
|
179
|
-
logger.info(f"Configuration updated: {key}={value}")
|
|
176
|
+
logger.debug(f"Configuration updated: {key}={value}")
|
|
180
177
|
|
|
181
178
|
|
|
182
179
|
@config.command("set")
|
|
@@ -254,8 +251,7 @@ def config_reset(ctx: click.Context, yes: bool) -> None:
|
|
|
254
251
|
save_config(DEFAULT_CONFIG.copy(), config_path)
|
|
255
252
|
click.echo("Configuration reset to defaults")
|
|
256
253
|
|
|
257
|
-
|
|
258
|
-
logger.info("Configuration reset to defaults")
|
|
254
|
+
logger.debug("Configuration reset to defaults")
|
|
259
255
|
except ConfigError as e:
|
|
260
256
|
click.echo(f"Error resetting configuration: {e}", err=True)
|
|
261
257
|
sys.exit(1)
|
|
@@ -460,7 +456,6 @@ def hello(ctx: click.Context, name: str, uppercase: bool) -> None:
|
|
|
460
456
|
thai-lint hello --name Bob --uppercase
|
|
461
457
|
"""
|
|
462
458
|
config = ctx.obj["config"]
|
|
463
|
-
verbose = ctx.obj.get("verbose", False)
|
|
464
459
|
|
|
465
460
|
# Get greeting from config or use default
|
|
466
461
|
greeting_template = config.get("greeting", "Hello")
|
|
@@ -474,5 +469,4 @@ def hello(ctx: click.Context, name: str, uppercase: bool) -> None:
|
|
|
474
469
|
# Output greeting
|
|
475
470
|
click.echo(message)
|
|
476
471
|
|
|
477
|
-
|
|
478
|
-
logger.info(f"Greeted {name} with template '{greeting_template}'")
|
|
472
|
+
logger.debug(f"Greeted {name} with template '{greeting_template}'")
|
|
@@ -6,8 +6,9 @@ Scope: Export and registration of all linter CLI commands (nesting, srp, dry, ma
|
|
|
6
6
|
Overview: Package initialization that imports all linter command modules to trigger their registration
|
|
7
7
|
with the main CLI group via Click decorators. Each submodule defines commands using @cli.command()
|
|
8
8
|
decorators that automatically register with the CLI when imported. Organized by logical grouping:
|
|
9
|
-
structure_quality (nesting, srp), code_smells (dry, magic-numbers), code_patterns (
|
|
9
|
+
structure_quality (nesting, srp), code_smells (dry, magic-numbers), code_patterns (improper-logging,
|
|
10
10
|
method-property, stateless-class), structure (file-placement, pipeline), documentation (file-header).
|
|
11
|
+
Note: print-statements is a deprecated alias for improper-logging.
|
|
11
12
|
|
|
12
13
|
Dependencies: Click for CLI framework, src.cli.main for CLI group, individual linter modules
|
|
13
14
|
|
|
@@ -34,6 +35,7 @@ from src.cli.linters import ( # noqa: F401
|
|
|
34
35
|
|
|
35
36
|
# Re-export command functions for testing and reference
|
|
36
37
|
from src.cli.linters.code_patterns import (
|
|
38
|
+
improper_logging,
|
|
37
39
|
method_property,
|
|
38
40
|
print_statements,
|
|
39
41
|
stateless_class,
|
|
@@ -52,7 +54,8 @@ __all__ = [
|
|
|
52
54
|
"dry",
|
|
53
55
|
"magic_numbers",
|
|
54
56
|
# Code pattern commands
|
|
55
|
-
"
|
|
57
|
+
"improper_logging",
|
|
58
|
+
"print_statements", # deprecated alias for improper_logging
|
|
56
59
|
"method_property",
|
|
57
60
|
"stateless_class",
|
|
58
61
|
# Structure commands
|