thailint 0.4.6__tar.gz → 0.5.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 (96) hide show
  1. {thailint-0.4.6 → thailint-0.5.0}/PKG-INFO +3 -3
  2. {thailint-0.4.6 → thailint-0.5.0}/README.md +2 -2
  3. {thailint-0.4.6 → thailint-0.5.0}/pyproject.toml +1 -1
  4. {thailint-0.4.6 → thailint-0.5.0}/src/cli.py +110 -0
  5. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/typescript_analyzer.py +1 -0
  6. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/typescript_analyzer.py +1 -0
  7. thailint-0.5.0/src/linters/print_statements/__init__.py +53 -0
  8. thailint-0.5.0/src/linters/print_statements/config.py +83 -0
  9. thailint-0.5.0/src/linters/print_statements/linter.py +430 -0
  10. thailint-0.5.0/src/linters/print_statements/python_analyzer.py +155 -0
  11. thailint-0.5.0/src/linters/print_statements/typescript_analyzer.py +135 -0
  12. thailint-0.5.0/src/linters/print_statements/violation_builder.py +98 -0
  13. {thailint-0.4.6 → thailint-0.5.0}/src/templates/thailint_config_template.yaml +26 -0
  14. {thailint-0.4.6 → thailint-0.5.0}/CHANGELOG.md +0 -0
  15. {thailint-0.4.6 → thailint-0.5.0}/LICENSE +0 -0
  16. {thailint-0.4.6 → thailint-0.5.0}/src/__init__.py +0 -0
  17. {thailint-0.4.6 → thailint-0.5.0}/src/analyzers/__init__.py +0 -0
  18. {thailint-0.4.6 → thailint-0.5.0}/src/analyzers/typescript_base.py +0 -0
  19. {thailint-0.4.6 → thailint-0.5.0}/src/api.py +0 -0
  20. {thailint-0.4.6 → thailint-0.5.0}/src/config.py +0 -0
  21. {thailint-0.4.6 → thailint-0.5.0}/src/core/__init__.py +0 -0
  22. {thailint-0.4.6 → thailint-0.5.0}/src/core/base.py +0 -0
  23. {thailint-0.4.6 → thailint-0.5.0}/src/core/cli_utils.py +0 -0
  24. {thailint-0.4.6 → thailint-0.5.0}/src/core/config_parser.py +0 -0
  25. {thailint-0.4.6 → thailint-0.5.0}/src/core/linter_utils.py +0 -0
  26. {thailint-0.4.6 → thailint-0.5.0}/src/core/registry.py +0 -0
  27. {thailint-0.4.6 → thailint-0.5.0}/src/core/rule_discovery.py +0 -0
  28. {thailint-0.4.6 → thailint-0.5.0}/src/core/types.py +0 -0
  29. {thailint-0.4.6 → thailint-0.5.0}/src/core/violation_builder.py +0 -0
  30. {thailint-0.4.6 → thailint-0.5.0}/src/linter_config/__init__.py +0 -0
  31. {thailint-0.4.6 → thailint-0.5.0}/src/linter_config/ignore.py +0 -0
  32. {thailint-0.4.6 → thailint-0.5.0}/src/linter_config/loader.py +0 -0
  33. {thailint-0.4.6 → thailint-0.5.0}/src/linters/__init__.py +0 -0
  34. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/__init__.py +0 -0
  35. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/base_token_analyzer.py +0 -0
  36. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/block_filter.py +0 -0
  37. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/block_grouper.py +0 -0
  38. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/cache.py +0 -0
  39. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/cache_query.py +0 -0
  40. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/config.py +0 -0
  41. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/config_loader.py +0 -0
  42. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/deduplicator.py +0 -0
  43. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/duplicate_storage.py +0 -0
  44. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/file_analyzer.py +0 -0
  45. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/inline_ignore.py +0 -0
  46. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/linter.py +0 -0
  47. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/python_analyzer.py +0 -0
  48. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/storage_initializer.py +0 -0
  49. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/token_hasher.py +0 -0
  50. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/typescript_analyzer.py +0 -0
  51. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/violation_builder.py +0 -0
  52. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/violation_filter.py +0 -0
  53. {thailint-0.4.6 → thailint-0.5.0}/src/linters/dry/violation_generator.py +0 -0
  54. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/__init__.py +0 -0
  55. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/atemporal_detector.py +0 -0
  56. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/config.py +0 -0
  57. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/field_validator.py +0 -0
  58. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/linter.py +0 -0
  59. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/python_parser.py +0 -0
  60. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_header/violation_builder.py +0 -0
  61. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/__init__.py +0 -0
  62. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/config_loader.py +0 -0
  63. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/directory_matcher.py +0 -0
  64. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/linter.py +0 -0
  65. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/path_resolver.py +0 -0
  66. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/pattern_matcher.py +0 -0
  67. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/pattern_validator.py +0 -0
  68. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/rule_checker.py +0 -0
  69. {thailint-0.4.6 → thailint-0.5.0}/src/linters/file_placement/violation_factory.py +0 -0
  70. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/__init__.py +0 -0
  71. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/config.py +0 -0
  72. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
  73. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/linter.py +0 -0
  74. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
  75. {thailint-0.4.6 → thailint-0.5.0}/src/linters/magic_numbers/violation_builder.py +0 -0
  76. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/__init__.py +0 -0
  77. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/config.py +0 -0
  78. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/linter.py +0 -0
  79. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/python_analyzer.py +0 -0
  80. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
  81. {thailint-0.4.6 → thailint-0.5.0}/src/linters/nesting/violation_builder.py +0 -0
  82. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/__init__.py +0 -0
  83. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/class_analyzer.py +0 -0
  84. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/config.py +0 -0
  85. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/heuristics.py +0 -0
  86. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/linter.py +0 -0
  87. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/metrics_evaluator.py +0 -0
  88. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/python_analyzer.py +0 -0
  89. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/typescript_analyzer.py +0 -0
  90. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
  91. {thailint-0.4.6 → thailint-0.5.0}/src/linters/srp/violation_builder.py +0 -0
  92. {thailint-0.4.6 → thailint-0.5.0}/src/orchestrator/__init__.py +0 -0
  93. {thailint-0.4.6 → thailint-0.5.0}/src/orchestrator/core.py +0 -0
  94. {thailint-0.4.6 → thailint-0.5.0}/src/orchestrator/language_detector.py +0 -0
  95. {thailint-0.4.6 → thailint-0.5.0}/src/utils/__init__.py +0 -0
  96. {thailint-0.4.6 → thailint-0.5.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.4.6
3
+ Version: 0.5.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
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
37
37
 
38
38
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
39
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
- [![Tests](https://img.shields.io/badge/tests-356%2F356%20passing-brightgreen.svg)](tests/)
40
+ [![Tests](https://img.shields.io/badge/tests-393%2F393%20passing-brightgreen.svg)](tests/)
41
41
  [![Coverage](https://img.shields.io/badge/coverage-87%25-brightgreen.svg)](htmlcov/)
42
42
  [![Documentation Status](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
43
43
 
@@ -122,7 +122,7 @@ cd thai-lint
122
122
  pip install -e ".[dev]"
123
123
  ```
124
124
 
125
- ### From PyPI (once published)
125
+ ### From PyPI
126
126
 
127
127
  ```bash
128
128
  pip install thai-lint
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
5
- [![Tests](https://img.shields.io/badge/tests-356%2F356%20passing-brightgreen.svg)](tests/)
5
+ [![Tests](https://img.shields.io/badge/tests-393%2F393%20passing-brightgreen.svg)](tests/)
6
6
  [![Coverage](https://img.shields.io/badge/coverage-87%25-brightgreen.svg)](htmlcov/)
7
7
  [![Documentation Status](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
8
8
 
@@ -87,7 +87,7 @@ cd thai-lint
87
87
  pip install -e ".[dev]"
88
88
  ```
89
89
 
90
- ### From PyPI (once published)
90
+ ### From PyPI
91
91
 
92
92
  ```bash
93
93
  pip install thai-lint
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
17
17
 
18
18
  [tool.poetry]
19
19
  name = "thailint"
20
- version = "0.4.6"
20
+ version = "0.5.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"
@@ -1551,5 +1551,115 @@ def _execute_magic_numbers_lint( # pylint: disable=too-many-arguments,too-many-
1551
1551
  sys.exit(1 if magic_numbers_violations else 0)
1552
1552
 
1553
1553
 
1554
+ # =============================================================================
1555
+ # Print Statements Linter Command
1556
+ # =============================================================================
1557
+
1558
+
1559
+ def _setup_print_statements_orchestrator(
1560
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
1561
+ ):
1562
+ """Set up orchestrator for print-statements command."""
1563
+ from src.orchestrator.core import Orchestrator
1564
+ from src.utils.project_root import get_project_root
1565
+
1566
+ if project_root is None:
1567
+ first_path = path_objs[0] if path_objs else Path.cwd()
1568
+ search_start = first_path if first_path.is_dir() else first_path.parent
1569
+ project_root = get_project_root(search_start)
1570
+
1571
+ orchestrator = Orchestrator(project_root=project_root)
1572
+
1573
+ if config_file:
1574
+ _load_config_file(orchestrator, config_file, verbose)
1575
+
1576
+ return orchestrator
1577
+
1578
+
1579
+ def _run_print_statements_lint(orchestrator, path_objs: list[Path], recursive: bool):
1580
+ """Execute print-statements lint on files or directories."""
1581
+ all_violations = _execute_linting_on_paths(orchestrator, path_objs, recursive)
1582
+ return [v for v in all_violations if "print-statement" in v.rule_id]
1583
+
1584
+
1585
+ @cli.command("print-statements")
1586
+ @click.argument("paths", nargs=-1, type=click.Path())
1587
+ @click.option("--config", "-c", "config_file", type=click.Path(), help="Path to config file")
1588
+ @format_option
1589
+ @click.option("--recursive/--no-recursive", default=True, help="Scan directories recursively")
1590
+ @click.pass_context
1591
+ def print_statements( # pylint: disable=too-many-arguments,too-many-positional-arguments
1592
+ ctx,
1593
+ paths: tuple[str, ...],
1594
+ config_file: str | None,
1595
+ format: str,
1596
+ recursive: bool,
1597
+ ):
1598
+ """Check for print/console statements in code.
1599
+
1600
+ Detects print() calls in Python and console.log/warn/error/debug/info calls
1601
+ in TypeScript/JavaScript that should be replaced with proper logging.
1602
+
1603
+ PATHS: Files or directories to lint (defaults to current directory if none provided)
1604
+
1605
+ Examples:
1606
+
1607
+ \b
1608
+ # Check current directory (all files recursively)
1609
+ thai-lint print-statements
1610
+
1611
+ \b
1612
+ # Check specific directory
1613
+ thai-lint print-statements src/
1614
+
1615
+ \b
1616
+ # Check single file
1617
+ thai-lint print-statements src/app.py
1618
+
1619
+ \b
1620
+ # Check multiple files
1621
+ thai-lint print-statements src/app.py src/utils.ts tests/test_app.py
1622
+
1623
+ \b
1624
+ # Get JSON output
1625
+ thai-lint print-statements --format json .
1626
+
1627
+ \b
1628
+ # Use custom config file
1629
+ thai-lint print-statements --config .thailint.yaml src/
1630
+ """
1631
+ verbose = ctx.obj.get("verbose", False)
1632
+ project_root = _get_project_root_from_context(ctx)
1633
+
1634
+ if not paths:
1635
+ paths = (".",)
1636
+
1637
+ path_objs = [Path(p) for p in paths]
1638
+
1639
+ try:
1640
+ _execute_print_statements_lint(
1641
+ path_objs, config_file, format, recursive, verbose, project_root
1642
+ )
1643
+ except Exception as e:
1644
+ _handle_linting_error(e, verbose)
1645
+
1646
+
1647
+ def _execute_print_statements_lint( # pylint: disable=too-many-arguments,too-many-positional-arguments
1648
+ path_objs, config_file, format, recursive, verbose, project_root=None
1649
+ ):
1650
+ """Execute print-statements lint."""
1651
+ _validate_paths_exist(path_objs)
1652
+ orchestrator = _setup_print_statements_orchestrator(
1653
+ path_objs, config_file, verbose, project_root
1654
+ )
1655
+ print_statements_violations = _run_print_statements_lint(orchestrator, path_objs, recursive)
1656
+
1657
+ if verbose:
1658
+ logger.info(f"Found {len(print_statements_violations)} print statement violation(s)")
1659
+
1660
+ format_violations(print_statements_violations, format)
1661
+ sys.exit(1 if print_statements_violations else 0)
1662
+
1663
+
1554
1664
  if __name__ == "__main__":
1555
1665
  cli()
@@ -26,6 +26,7 @@ from typing import Any
26
26
 
27
27
  from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
28
28
 
29
+ # dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
29
30
  try:
30
31
  from tree_sitter import Node
31
32
 
@@ -22,6 +22,7 @@ from typing import Any
22
22
 
23
23
  from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
24
24
 
25
+ # dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
25
26
  try:
26
27
  from tree_sitter import Node
27
28
 
@@ -0,0 +1,53 @@
1
+ """
2
+ File: src/linters/print_statements/__init__.py
3
+
4
+ Purpose: Print statements linter package exports and convenience functions
5
+
6
+ Exports: PrintStatementRule class, PrintStatementConfig dataclass, lint() convenience function
7
+
8
+ Depends: .linter for PrintStatementRule, .config for PrintStatementConfig
9
+
10
+ Implements: lint(file_path, config) -> list[Violation] for simple linting operations
11
+
12
+ Related: src/linters/magic_numbers/__init__.py, src/core/base.py
13
+
14
+ Overview: Provides the public interface for the print statements linter package. Exports main
15
+ PrintStatementRule class for use by the orchestrator and PrintStatementConfig for configuration.
16
+ Includes lint() convenience function that provides a simple API for running the print statements
17
+ linter on a file without directly interacting with the orchestrator. This module serves as the
18
+ entry point for users of the print statements linter, hiding implementation details and exposing
19
+ only the essential components needed for linting operations.
20
+
21
+ Usage: from src.linters.print_statements import PrintStatementRule, lint
22
+ violations = lint("path/to/file.py")
23
+
24
+ Notes: Module-level exports with __all__ definition, convenience function wrapper
25
+ """
26
+
27
+ from .config import PrintStatementConfig
28
+ from .linter import PrintStatementRule
29
+
30
+ __all__ = ["PrintStatementRule", "PrintStatementConfig", "lint"]
31
+
32
+
33
+ def lint(file_path: str, config: dict | None = None) -> list:
34
+ """Convenience function for linting a file for print statements.
35
+
36
+ Args:
37
+ file_path: Path to the file to lint
38
+ config: Optional configuration dictionary
39
+
40
+ Returns:
41
+ List of violations found
42
+ """
43
+ from pathlib import Path
44
+
45
+ from src.orchestrator.core import FileLintContext
46
+
47
+ rule = PrintStatementRule()
48
+ context = FileLintContext(
49
+ path=Path(file_path),
50
+ lang="python",
51
+ )
52
+
53
+ return rule.check(context)
@@ -0,0 +1,83 @@
1
+ """
2
+ File: src/linters/print_statements/config.py
3
+
4
+ Purpose: Configuration schema for print statements linter
5
+
6
+ Exports: PrintStatementConfig dataclass
7
+
8
+ Depends: dataclasses, typing
9
+
10
+ Implements: PrintStatementConfig(enabled, ignore, allow_in_scripts, console_methods),
11
+ from_dict class method for loading configuration from dictionary
12
+
13
+ Related: src/linters/magic_numbers/config.py, src/core/types.py
14
+
15
+ Overview: Defines configuration schema for print statements linter. Provides PrintStatementConfig
16
+ dataclass with enabled flag, ignore patterns list, allow_in_scripts setting (default True to
17
+ allow print in __main__ blocks), and console_methods set (default includes log, warn, error,
18
+ debug, info) for TypeScript/JavaScript console method detection. Supports per-file and
19
+ per-directory config overrides through from_dict class method. Integrates with orchestrator's
20
+ configuration system to allow users to customize detection via .thailint.yaml configuration.
21
+
22
+ Usage: config = PrintStatementConfig.from_dict(yaml_config, language="python")
23
+
24
+ Notes: Dataclass with defaults matching common use cases, language-specific override support
25
+ """
26
+
27
+ from dataclasses import dataclass, field
28
+ from typing import Any
29
+
30
+
31
+ @dataclass
32
+ class PrintStatementConfig:
33
+ """Configuration for print statements linter."""
34
+
35
+ enabled: bool = True
36
+ ignore: list[str] = field(default_factory=list)
37
+ allow_in_scripts: bool = True
38
+ console_methods: set[str] = field(
39
+ default_factory=lambda: {"log", "warn", "error", "debug", "info"}
40
+ )
41
+
42
+ @classmethod
43
+ def from_dict(
44
+ cls, config: dict[str, Any], language: str | None = None
45
+ ) -> "PrintStatementConfig":
46
+ """Load configuration from dictionary with language-specific overrides.
47
+
48
+ Args:
49
+ config: Dictionary containing configuration values
50
+ language: Programming language (python, typescript, javascript)
51
+ for language-specific settings
52
+
53
+ Returns:
54
+ PrintStatementConfig instance with values from dictionary
55
+ """
56
+ # Get language-specific config if available
57
+ if language and language in config:
58
+ lang_config = config[language]
59
+ allow_in_scripts = lang_config.get(
60
+ "allow_in_scripts", config.get("allow_in_scripts", True)
61
+ )
62
+ console_methods = set(
63
+ lang_config.get(
64
+ "console_methods",
65
+ config.get("console_methods", ["log", "warn", "error", "debug", "info"]),
66
+ )
67
+ )
68
+ else:
69
+ allow_in_scripts = config.get("allow_in_scripts", True)
70
+ console_methods = set(
71
+ config.get("console_methods", ["log", "warn", "error", "debug", "info"])
72
+ )
73
+
74
+ ignore_patterns = config.get("ignore", [])
75
+ if not isinstance(ignore_patterns, list):
76
+ ignore_patterns = []
77
+
78
+ return cls(
79
+ enabled=config.get("enabled", True),
80
+ ignore=ignore_patterns,
81
+ allow_in_scripts=allow_in_scripts,
82
+ console_methods=console_methods,
83
+ )