thailint 0.5.0__py3-none-any.whl → 0.15.3__py3-none-any.whl

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 (204) hide show
  1. src/__init__.py +1 -0
  2. src/analyzers/__init__.py +4 -3
  3. src/analyzers/ast_utils.py +54 -0
  4. src/analyzers/rust_base.py +155 -0
  5. src/analyzers/rust_context.py +141 -0
  6. src/analyzers/typescript_base.py +4 -0
  7. src/cli/__init__.py +30 -0
  8. src/cli/__main__.py +22 -0
  9. src/cli/config.py +480 -0
  10. src/cli/config_merge.py +241 -0
  11. src/cli/linters/__init__.py +67 -0
  12. src/cli/linters/code_patterns.py +270 -0
  13. src/cli/linters/code_smells.py +342 -0
  14. src/cli/linters/documentation.py +83 -0
  15. src/cli/linters/performance.py +287 -0
  16. src/cli/linters/shared.py +331 -0
  17. src/cli/linters/structure.py +327 -0
  18. src/cli/linters/structure_quality.py +328 -0
  19. src/cli/main.py +120 -0
  20. src/cli/utils.py +395 -0
  21. src/cli_main.py +37 -0
  22. src/config.py +38 -25
  23. src/core/base.py +7 -2
  24. src/core/cli_utils.py +19 -2
  25. src/core/config_parser.py +5 -2
  26. src/core/constants.py +54 -0
  27. src/core/linter_utils.py +95 -6
  28. src/core/python_lint_rule.py +101 -0
  29. src/core/registry.py +1 -1
  30. src/core/rule_discovery.py +147 -84
  31. src/core/types.py +13 -0
  32. src/core/violation_builder.py +78 -15
  33. src/core/violation_utils.py +69 -0
  34. src/formatters/__init__.py +22 -0
  35. src/formatters/sarif.py +202 -0
  36. src/linter_config/directive_markers.py +109 -0
  37. src/linter_config/ignore.py +254 -395
  38. src/linter_config/loader.py +45 -12
  39. src/linter_config/pattern_utils.py +65 -0
  40. src/linter_config/rule_matcher.py +89 -0
  41. src/linters/collection_pipeline/__init__.py +90 -0
  42. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  43. src/linters/collection_pipeline/ast_utils.py +40 -0
  44. src/linters/collection_pipeline/config.py +75 -0
  45. src/linters/collection_pipeline/continue_analyzer.py +94 -0
  46. src/linters/collection_pipeline/detector.py +360 -0
  47. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  48. src/linters/collection_pipeline/linter.py +420 -0
  49. src/linters/collection_pipeline/suggestion_builder.py +130 -0
  50. src/linters/cqs/__init__.py +54 -0
  51. src/linters/cqs/config.py +55 -0
  52. src/linters/cqs/function_analyzer.py +201 -0
  53. src/linters/cqs/input_detector.py +139 -0
  54. src/linters/cqs/linter.py +159 -0
  55. src/linters/cqs/output_detector.py +84 -0
  56. src/linters/cqs/python_analyzer.py +54 -0
  57. src/linters/cqs/types.py +82 -0
  58. src/linters/cqs/typescript_cqs_analyzer.py +61 -0
  59. src/linters/cqs/typescript_function_analyzer.py +192 -0
  60. src/linters/cqs/typescript_input_detector.py +203 -0
  61. src/linters/cqs/typescript_output_detector.py +117 -0
  62. src/linters/cqs/violation_builder.py +94 -0
  63. src/linters/dry/base_token_analyzer.py +16 -9
  64. src/linters/dry/block_filter.py +120 -20
  65. src/linters/dry/block_grouper.py +4 -0
  66. src/linters/dry/cache.py +104 -10
  67. src/linters/dry/cache_query.py +4 -0
  68. src/linters/dry/config.py +54 -11
  69. src/linters/dry/constant.py +92 -0
  70. src/linters/dry/constant_matcher.py +223 -0
  71. src/linters/dry/constant_violation_builder.py +98 -0
  72. src/linters/dry/duplicate_storage.py +5 -4
  73. src/linters/dry/file_analyzer.py +4 -2
  74. src/linters/dry/inline_ignore.py +7 -16
  75. src/linters/dry/linter.py +183 -48
  76. src/linters/dry/python_analyzer.py +60 -439
  77. src/linters/dry/python_constant_extractor.py +100 -0
  78. src/linters/dry/single_statement_detector.py +417 -0
  79. src/linters/dry/token_hasher.py +116 -112
  80. src/linters/dry/typescript_analyzer.py +68 -382
  81. src/linters/dry/typescript_constant_extractor.py +138 -0
  82. src/linters/dry/typescript_statement_detector.py +255 -0
  83. src/linters/dry/typescript_value_extractor.py +70 -0
  84. src/linters/dry/violation_builder.py +4 -0
  85. src/linters/dry/violation_filter.py +5 -4
  86. src/linters/dry/violation_generator.py +71 -14
  87. src/linters/file_header/atemporal_detector.py +68 -50
  88. src/linters/file_header/base_parser.py +93 -0
  89. src/linters/file_header/bash_parser.py +66 -0
  90. src/linters/file_header/config.py +90 -16
  91. src/linters/file_header/css_parser.py +70 -0
  92. src/linters/file_header/field_validator.py +36 -33
  93. src/linters/file_header/linter.py +140 -144
  94. src/linters/file_header/markdown_parser.py +130 -0
  95. src/linters/file_header/python_parser.py +14 -58
  96. src/linters/file_header/typescript_parser.py +73 -0
  97. src/linters/file_header/violation_builder.py +13 -12
  98. src/linters/file_placement/config_loader.py +3 -1
  99. src/linters/file_placement/directory_matcher.py +4 -0
  100. src/linters/file_placement/linter.py +66 -34
  101. src/linters/file_placement/pattern_matcher.py +41 -6
  102. src/linters/file_placement/pattern_validator.py +31 -12
  103. src/linters/file_placement/rule_checker.py +12 -7
  104. src/linters/lazy_ignores/__init__.py +43 -0
  105. src/linters/lazy_ignores/config.py +74 -0
  106. src/linters/lazy_ignores/directive_utils.py +164 -0
  107. src/linters/lazy_ignores/header_parser.py +177 -0
  108. src/linters/lazy_ignores/linter.py +158 -0
  109. src/linters/lazy_ignores/matcher.py +168 -0
  110. src/linters/lazy_ignores/python_analyzer.py +209 -0
  111. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  112. src/linters/lazy_ignores/skip_detector.py +298 -0
  113. src/linters/lazy_ignores/types.py +71 -0
  114. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  115. src/linters/lazy_ignores/violation_builder.py +135 -0
  116. src/linters/lbyl/__init__.py +31 -0
  117. src/linters/lbyl/config.py +63 -0
  118. src/linters/lbyl/linter.py +67 -0
  119. src/linters/lbyl/pattern_detectors/__init__.py +53 -0
  120. src/linters/lbyl/pattern_detectors/base.py +63 -0
  121. src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
  122. src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
  123. src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
  124. src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
  125. src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
  126. src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
  127. src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
  128. src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
  129. src/linters/lbyl/python_analyzer.py +215 -0
  130. src/linters/lbyl/violation_builder.py +354 -0
  131. src/linters/magic_numbers/context_analyzer.py +227 -225
  132. src/linters/magic_numbers/linter.py +28 -82
  133. src/linters/magic_numbers/python_analyzer.py +4 -16
  134. src/linters/magic_numbers/typescript_analyzer.py +9 -12
  135. src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
  136. src/linters/method_property/__init__.py +49 -0
  137. src/linters/method_property/config.py +138 -0
  138. src/linters/method_property/linter.py +414 -0
  139. src/linters/method_property/python_analyzer.py +473 -0
  140. src/linters/method_property/violation_builder.py +119 -0
  141. src/linters/nesting/linter.py +24 -16
  142. src/linters/nesting/python_analyzer.py +4 -0
  143. src/linters/nesting/typescript_analyzer.py +6 -12
  144. src/linters/nesting/violation_builder.py +1 -0
  145. src/linters/performance/__init__.py +91 -0
  146. src/linters/performance/config.py +43 -0
  147. src/linters/performance/constants.py +49 -0
  148. src/linters/performance/linter.py +149 -0
  149. src/linters/performance/python_analyzer.py +365 -0
  150. src/linters/performance/regex_analyzer.py +312 -0
  151. src/linters/performance/regex_linter.py +139 -0
  152. src/linters/performance/typescript_analyzer.py +236 -0
  153. src/linters/performance/violation_builder.py +160 -0
  154. src/linters/print_statements/config.py +7 -12
  155. src/linters/print_statements/linter.py +26 -43
  156. src/linters/print_statements/python_analyzer.py +91 -93
  157. src/linters/print_statements/typescript_analyzer.py +15 -25
  158. src/linters/print_statements/violation_builder.py +12 -14
  159. src/linters/srp/class_analyzer.py +11 -7
  160. src/linters/srp/heuristics.py +56 -22
  161. src/linters/srp/linter.py +15 -16
  162. src/linters/srp/python_analyzer.py +55 -20
  163. src/linters/srp/typescript_metrics_calculator.py +110 -50
  164. src/linters/stateless_class/__init__.py +25 -0
  165. src/linters/stateless_class/config.py +58 -0
  166. src/linters/stateless_class/linter.py +349 -0
  167. src/linters/stateless_class/python_analyzer.py +290 -0
  168. src/linters/stringly_typed/__init__.py +36 -0
  169. src/linters/stringly_typed/config.py +189 -0
  170. src/linters/stringly_typed/context_filter.py +451 -0
  171. src/linters/stringly_typed/function_call_violation_builder.py +135 -0
  172. src/linters/stringly_typed/ignore_checker.py +100 -0
  173. src/linters/stringly_typed/ignore_utils.py +51 -0
  174. src/linters/stringly_typed/linter.py +376 -0
  175. src/linters/stringly_typed/python/__init__.py +33 -0
  176. src/linters/stringly_typed/python/analyzer.py +348 -0
  177. src/linters/stringly_typed/python/call_tracker.py +175 -0
  178. src/linters/stringly_typed/python/comparison_tracker.py +257 -0
  179. src/linters/stringly_typed/python/condition_extractor.py +134 -0
  180. src/linters/stringly_typed/python/conditional_detector.py +179 -0
  181. src/linters/stringly_typed/python/constants.py +21 -0
  182. src/linters/stringly_typed/python/match_analyzer.py +94 -0
  183. src/linters/stringly_typed/python/validation_detector.py +189 -0
  184. src/linters/stringly_typed/python/variable_extractor.py +96 -0
  185. src/linters/stringly_typed/storage.py +620 -0
  186. src/linters/stringly_typed/storage_initializer.py +45 -0
  187. src/linters/stringly_typed/typescript/__init__.py +28 -0
  188. src/linters/stringly_typed/typescript/analyzer.py +157 -0
  189. src/linters/stringly_typed/typescript/call_tracker.py +335 -0
  190. src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
  191. src/linters/stringly_typed/violation_generator.py +419 -0
  192. src/orchestrator/core.py +252 -14
  193. src/orchestrator/language_detector.py +5 -3
  194. src/templates/thailint_config_template.yaml +196 -0
  195. src/utils/project_root.py +3 -0
  196. thailint-0.15.3.dist-info/METADATA +187 -0
  197. thailint-0.15.3.dist-info/RECORD +226 -0
  198. thailint-0.15.3.dist-info/entry_points.txt +4 -0
  199. src/cli.py +0 -1665
  200. thailint-0.5.0.dist-info/METADATA +0 -1286
  201. thailint-0.5.0.dist-info/RECORD +0 -96
  202. thailint-0.5.0.dist-info/entry_points.txt +0 -4
  203. {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +0 -0
  204. {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/licenses/LICENSE +0 -0
@@ -13,13 +13,17 @@ Overview: Provides base classes and data structures for violation creation acros
13
13
 
14
14
  Dependencies: dataclasses, src.core.types (Violation, Severity)
15
15
 
16
- Exports: ViolationInfo dataclass, BaseViolationBuilder class
16
+ Exports: ViolationInfo dataclass, build_violation function, build_violation_from_params function,
17
+ BaseViolationBuilder class (compat)
17
18
 
18
19
  Interfaces: ViolationInfo(rule_id, file_path, line, message, column, severity),
19
- BaseViolationBuilder.build(info: ViolationInfo) -> Violation
20
+ build_violation(info: ViolationInfo) -> Violation
20
21
 
21
- Implementation: Uses dataclass for type-safe violation info, base class provides build()
22
- method that constructs Violation objects with proper defaults
22
+ Implementation: Uses dataclass for type-safe violation info, functions provide build logic
23
+ that constructs Violation objects with proper defaults
24
+
25
+ Suppressions:
26
+ - too-many-arguments,too-many-positional-arguments: Violation fields as parameters
23
27
  """
24
28
 
25
29
  from dataclasses import dataclass
@@ -50,14 +54,82 @@ class ViolationInfo:
50
54
  suggestion: str | None = None
51
55
 
52
56
 
57
+ def build_violation(info: ViolationInfo) -> Violation:
58
+ """Build a Violation from ViolationInfo.
59
+
60
+ Args:
61
+ info: ViolationInfo containing all violation details
62
+
63
+ Returns:
64
+ Violation object with all fields populated
65
+ """
66
+ return Violation(
67
+ rule_id=info.rule_id,
68
+ file_path=info.file_path,
69
+ line=info.line,
70
+ column=info.column,
71
+ message=info.message,
72
+ severity=info.severity,
73
+ suggestion=info.suggestion,
74
+ )
75
+
76
+
77
+ def build_violation_from_params( # pylint: disable=too-many-arguments,too-many-positional-arguments
78
+ rule_id: str,
79
+ file_path: str,
80
+ line: int,
81
+ message: str,
82
+ column: int = 1,
83
+ severity: Severity = Severity.ERROR,
84
+ suggestion: str | None = None,
85
+ ) -> Violation:
86
+ """Build a Violation directly from parameters.
87
+
88
+ Note: Pylint too-many-arguments disabled. This convenience function mirrors the
89
+ ViolationInfo dataclass fields (7 parameters, 3 with defaults). The alternative
90
+ would require every caller to create ViolationInfo objects manually, reducing
91
+ readability.
92
+
93
+ Args:
94
+ rule_id: Identifier for the rule that was violated
95
+ file_path: Path to the file containing the violation
96
+ line: Line number where violation occurs (1-indexed)
97
+ message: Description of the violation
98
+ column: Column number where violation occurs (0-indexed, default=1)
99
+ severity: Severity level of the violation (default=ERROR)
100
+ suggestion: Optional suggestion for fixing the violation
101
+
102
+ Returns:
103
+ Violation object with all fields populated
104
+ """
105
+ info = ViolationInfo(
106
+ rule_id=rule_id,
107
+ file_path=file_path,
108
+ line=line,
109
+ message=message,
110
+ column=column,
111
+ severity=severity,
112
+ suggestion=suggestion,
113
+ )
114
+ return build_violation(info)
115
+
116
+
117
+ # Legacy class wrapper for backward compatibility
53
118
  class BaseViolationBuilder:
54
119
  """Base class for building violations with consistent structure.
55
120
 
56
121
  Provides common build() method for creating Violation objects from ViolationInfo.
57
122
  Linter-specific builders extend this class to add their domain-specific violation
58
123
  creation methods while inheriting the common construction logic.
124
+
125
+ Note: This class is a thin wrapper around module-level functions
126
+ for backward compatibility.
59
127
  """
60
128
 
129
+ def __init__(self) -> None:
130
+ """Initialize the builder."""
131
+ pass # No state needed
132
+
61
133
  def build(self, info: ViolationInfo) -> Violation:
62
134
  """Build a Violation from ViolationInfo.
63
135
 
@@ -67,15 +139,7 @@ class BaseViolationBuilder:
67
139
  Returns:
68
140
  Violation object with all fields populated
69
141
  """
70
- return Violation(
71
- rule_id=info.rule_id,
72
- file_path=info.file_path,
73
- line=info.line,
74
- column=info.column,
75
- message=info.message,
76
- severity=info.severity,
77
- suggestion=info.suggestion,
78
- )
142
+ return build_violation(info)
79
143
 
80
144
  def build_from_params( # pylint: disable=too-many-arguments,too-many-positional-arguments
81
145
  self,
@@ -110,7 +174,7 @@ class BaseViolationBuilder:
110
174
  Returns:
111
175
  Violation object with all fields populated
112
176
  """
113
- info = ViolationInfo(
177
+ return build_violation_from_params(
114
178
  rule_id=rule_id,
115
179
  file_path=file_path,
116
180
  line=line,
@@ -119,4 +183,3 @@ class BaseViolationBuilder:
119
183
  severity=severity,
120
184
  suggestion=suggestion,
121
185
  )
122
- return self.build(info)
@@ -0,0 +1,69 @@
1
+ """
2
+ Purpose: Shared utility functions for violation processing across linters
3
+
4
+ Scope: Common violation-related operations used by multiple linters
5
+
6
+ Overview: Provides shared utility functions for working with violations, including
7
+ extracting line text and checking for ignore directives. These patterns were
8
+ previously duplicated across multiple linter modules (magic_numbers, print_statements,
9
+ method_property). Centralizing them here improves maintainability and ensures
10
+ consistent behavior across all linters.
11
+
12
+ Dependencies: BaseLintContext, Violation types
13
+
14
+ Exports: get_violation_line, has_python_noqa, has_typescript_noqa
15
+
16
+ Interfaces:
17
+ get_violation_line(violation, context) -> str | None
18
+ has_python_noqa(line_text) -> bool
19
+ has_typescript_noqa(line_text) -> bool
20
+
21
+ Implementation: Simple text extraction and pattern matching
22
+ """
23
+
24
+ from src.core.base import BaseLintContext
25
+ from src.core.types import Violation
26
+
27
+
28
+ def get_violation_line(violation: Violation, context: BaseLintContext) -> str | None:
29
+ """Get the line text for a violation, lowercased.
30
+
31
+ Args:
32
+ violation: Violation to get line for
33
+ context: Lint context with file content
34
+
35
+ Returns:
36
+ Lowercased line text, or None if not available
37
+ """
38
+ if not context.file_content:
39
+ return None
40
+
41
+ lines = context.file_content.splitlines()
42
+ if violation.line <= 0 or violation.line > len(lines):
43
+ return None
44
+
45
+ return lines[violation.line - 1].lower()
46
+
47
+
48
+ def has_python_noqa(line_text: str) -> bool:
49
+ """Check if line has Python-style noqa directive.
50
+
51
+ Args:
52
+ line_text: Lowercased line text
53
+
54
+ Returns:
55
+ True if line has # noqa comment
56
+ """
57
+ return "# noqa" in line_text
58
+
59
+
60
+ def has_typescript_noqa(line_text: str) -> bool:
61
+ """Check if line has TypeScript-style noqa directive.
62
+
63
+ Args:
64
+ line_text: Lowercased line text
65
+
66
+ Returns:
67
+ True if line has // noqa comment
68
+ """
69
+ return "// noqa" in line_text
@@ -0,0 +1,22 @@
1
+ """
2
+ Purpose: SARIF formatter package for thai-lint output
3
+
4
+ Scope: SARIF v2.1.0 formatter implementation and package exports
5
+
6
+ Overview: Formatters package providing SARIF (Static Analysis Results Interchange Format) v2.1.0
7
+ output generation from thai-lint Violation objects. Enables integration with GitHub Code
8
+ Scanning, Azure DevOps, VS Code SARIF Viewer, and other industry-standard CI/CD platforms.
9
+ Provides the SarifFormatter class for converting violations to SARIF JSON documents.
10
+
11
+ Dependencies: sarif module for SarifFormatter class
12
+
13
+ Exports: SarifFormatter class from sarif.py module
14
+
15
+ Interfaces: from src.formatters.sarif import SarifFormatter
16
+
17
+ Implementation: Package initialization with SarifFormatter export
18
+ """
19
+
20
+ from src.formatters.sarif import SarifFormatter
21
+
22
+ __all__ = ["SarifFormatter"]
@@ -0,0 +1,202 @@
1
+ """
2
+ Purpose: SARIF v2.1.0 formatter for converting Violation objects to SARIF JSON documents
3
+
4
+ Scope: SARIF document generation, tool metadata, result conversion, location mapping
5
+
6
+ Overview: Implements SarifFormatter class that converts thai-lint Violation objects to SARIF
7
+ (Static Analysis Results Interchange Format) v2.1.0 compliant JSON documents. Produces
8
+ output compatible with GitHub Code Scanning, Azure DevOps, VS Code SARIF Viewer, and
9
+ other industry-standard static analysis tools. Handles proper field mapping including
10
+ 1-indexed column conversion, rule metadata deduplication, and tool versioning.
11
+
12
+ Dependencies: src (for __version__), src.core.types (Violation, Severity)
13
+
14
+ Exports: SarifFormatter class with format() method
15
+
16
+ Interfaces: SarifFormatter.format(violations: list[Violation]) -> dict
17
+
18
+ Implementation: Converts Violation objects to SARIF structure with proper indexing and metadata
19
+ """
20
+
21
+ from typing import Any
22
+
23
+ from src import __version__
24
+ from src.core.types import Violation
25
+
26
+
27
+ class SarifFormatter:
28
+ """Formats Violation objects as SARIF v2.1.0 JSON documents.
29
+
30
+ SARIF (Static Analysis Results Interchange Format) is the OASIS standard
31
+ for static analysis tool output, enabling integration with GitHub Code
32
+ Scanning, Azure DevOps, and other CI/CD platforms.
33
+
34
+ Attributes:
35
+ tool_name: Name of the tool in SARIF output (default: "thai-lint")
36
+ tool_version: Version string for the tool (default: package version)
37
+ information_uri: URL for tool documentation
38
+ """
39
+
40
+ SARIF_VERSION = "2.1.0"
41
+ SARIF_SCHEMA = (
42
+ "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/"
43
+ "main/sarif-2.1/schema/sarif-schema-2.1.0.json"
44
+ )
45
+ DEFAULT_INFORMATION_URI = "https://github.com/be-wise-be-kind/thai-lint"
46
+
47
+ def __init__(
48
+ self,
49
+ tool_name: str = "thai-lint",
50
+ tool_version: str | None = None,
51
+ information_uri: str | None = None,
52
+ ) -> None:
53
+ """Initialize SarifFormatter with tool metadata.
54
+
55
+ Args:
56
+ tool_name: Name of the tool (default: "thai-lint")
57
+ tool_version: Version string (default: package __version__)
58
+ information_uri: URL for tool documentation
59
+ """
60
+ self.tool_name = tool_name
61
+ self.tool_version = tool_version or __version__
62
+ self.information_uri = information_uri or self.DEFAULT_INFORMATION_URI
63
+
64
+ def format(self, violations: list[Violation]) -> dict[str, Any]:
65
+ """Convert violations to SARIF v2.1.0 document.
66
+
67
+ Args:
68
+ violations: List of Violation objects to format
69
+
70
+ Returns:
71
+ SARIF v2.1.0 compliant dictionary ready for JSON serialization
72
+ """
73
+ return {
74
+ "version": self.SARIF_VERSION,
75
+ "$schema": self.SARIF_SCHEMA,
76
+ "runs": [self._create_run(violations)],
77
+ }
78
+
79
+ def _create_run(self, violations: list[Violation]) -> dict[str, Any]:
80
+ """Create a SARIF run object containing tool and results.
81
+
82
+ Args:
83
+ violations: List of violations for this run
84
+
85
+ Returns:
86
+ SARIF run object with tool and results
87
+ """
88
+ return {
89
+ "tool": self._create_tool(violations),
90
+ "results": [self._create_result(v) for v in violations],
91
+ }
92
+
93
+ def _create_tool(self, violations: list[Violation]) -> dict[str, Any]:
94
+ """Create SARIF tool object with driver metadata.
95
+
96
+ Args:
97
+ violations: List of violations to extract rule metadata from
98
+
99
+ Returns:
100
+ SARIF tool object with driver
101
+ """
102
+ return {
103
+ "driver": {
104
+ "name": self.tool_name,
105
+ "version": self.tool_version,
106
+ "informationUri": self.information_uri,
107
+ "rules": self._create_rules(violations),
108
+ }
109
+ }
110
+
111
+ def _create_rules(self, violations: list[Violation]) -> list[dict[str, Any]]:
112
+ """Create deduplicated SARIF rules array from violations.
113
+
114
+ Args:
115
+ violations: List of violations to extract unique rules from
116
+
117
+ Returns:
118
+ List of SARIF rule objects with unique IDs
119
+ """
120
+ seen_rule_ids: set[str] = set()
121
+ rules: list[dict[str, Any]] = []
122
+
123
+ for violation in violations:
124
+ if violation.rule_id not in seen_rule_ids:
125
+ seen_rule_ids.add(violation.rule_id)
126
+ rules.append(self._create_rule(violation))
127
+
128
+ return rules
129
+
130
+ def _create_rule(self, violation: Violation) -> dict[str, Any]:
131
+ """Create SARIF rule object from violation.
132
+
133
+ Args:
134
+ violation: Violation to extract rule metadata from
135
+
136
+ Returns:
137
+ SARIF rule object with id and shortDescription
138
+ """
139
+ # Extract rule category from rule_id (e.g., "nesting" from "nesting.excessive-depth")
140
+ parts = violation.rule_id.split(".")
141
+ category = parts[0] if parts else violation.rule_id
142
+
143
+ descriptions: dict[str, str] = {
144
+ "file-placement": "File placement violation",
145
+ "nesting": "Nesting depth violation",
146
+ "srp": "Single Responsibility Principle violation",
147
+ "dry": "Don't Repeat Yourself violation",
148
+ "magic-number": "Magic number violation",
149
+ "magic-numbers": "Magic number violation",
150
+ "file-header": "File header violation",
151
+ "print-statements": "Print statement violation",
152
+ }
153
+
154
+ description = descriptions.get(category, f"Rule: {violation.rule_id}")
155
+
156
+ return {
157
+ "id": violation.rule_id,
158
+ "shortDescription": {
159
+ "text": description,
160
+ },
161
+ }
162
+
163
+ def _create_result(self, violation: Violation) -> dict[str, Any]:
164
+ """Create SARIF result object from violation.
165
+
166
+ Args:
167
+ violation: Violation to convert to SARIF result
168
+
169
+ Returns:
170
+ SARIF result object with ruleId, level, message, locations
171
+ """
172
+ # thai-lint uses binary severity (ERROR only), map all to "error" level
173
+ return {
174
+ "ruleId": violation.rule_id,
175
+ "level": "error",
176
+ "message": {
177
+ "text": violation.message,
178
+ },
179
+ "locations": [self._create_location(violation)],
180
+ }
181
+
182
+ def _create_location(self, violation: Violation) -> dict[str, Any]:
183
+ """Create SARIF location object from violation.
184
+
185
+ Args:
186
+ violation: Violation with location information
187
+
188
+ Returns:
189
+ SARIF location object with physicalLocation
190
+ """
191
+ return {
192
+ "physicalLocation": {
193
+ "artifactLocation": {
194
+ "uri": violation.file_path,
195
+ },
196
+ "region": {
197
+ "startLine": violation.line,
198
+ # SARIF uses 1-indexed columns, Violation uses 0-indexed
199
+ "startColumn": violation.column + 1,
200
+ },
201
+ }
202
+ }
@@ -0,0 +1,109 @@
1
+ """
2
+ Purpose: Ignore directive marker detection for thailint comments
3
+
4
+ Scope: Detection of thailint and design-lint ignore markers in source code
5
+
6
+ Overview: Provides functions for detecting various ignore directive markers in code
7
+ comments. Supports file-level ignores, line-level ignores, block ignores, and
8
+ next-line ignores. Works with both Python (#) and JavaScript (//) comment styles.
9
+ All checks are case-insensitive.
10
+
11
+ Dependencies: None (pure string operations)
12
+
13
+ Exports: Marker detection functions for various ignore directive types
14
+
15
+ Interfaces: has_*_marker(line) -> bool for each marker type
16
+
17
+ Implementation: String-based pattern detection with case-insensitive matching
18
+ """
19
+
20
+
21
+ def has_ignore_directive_marker(line: str) -> bool:
22
+ """Check if line contains a file-level ignore directive marker.
23
+
24
+ Args:
25
+ line: Line of code to check
26
+
27
+ Returns:
28
+ True if line has ignore-file marker
29
+ """
30
+ line_lower = line.lower()
31
+ return "# thailint: ignore-file" in line_lower or "# design-lint: ignore-file" in line_lower
32
+
33
+
34
+ def has_line_ignore_marker(code: str) -> bool:
35
+ """Check if code line has an inline ignore marker.
36
+
37
+ Args:
38
+ code: Line of code to check
39
+
40
+ Returns:
41
+ True if line has inline ignore marker
42
+ """
43
+ code_lower = code.lower()
44
+ return (
45
+ "# thailint: ignore" in code_lower
46
+ or "# design-lint: ignore" in code_lower
47
+ or "// thailint: ignore" in code_lower
48
+ or "// design-lint: ignore" in code_lower
49
+ )
50
+
51
+
52
+ def has_ignore_next_line_marker(line: str) -> bool:
53
+ """Check if line has ignore-next-line marker.
54
+
55
+ Args:
56
+ line: Line of code to check
57
+
58
+ Returns:
59
+ True if line has ignore-next-line marker
60
+ """
61
+ return "# thailint: ignore-next-line" in line or "# design-lint: ignore-next-line" in line
62
+
63
+
64
+ def has_ignore_start_marker(line: str) -> bool:
65
+ """Check if line has ignore-start comment marker.
66
+
67
+ Only matches actual comment lines (starting with # or //), not strings
68
+ containing the marker text.
69
+
70
+ Args:
71
+ line: Line of code to check
72
+
73
+ Returns:
74
+ True if line is a proper ignore-start comment
75
+ """
76
+ stripped = line.strip().lower()
77
+ if not (stripped.startswith("#") or stripped.startswith("//")):
78
+ return False
79
+ return "ignore-start" in stripped and ("thailint:" in stripped or "design-lint:" in stripped)
80
+
81
+
82
+ def has_ignore_end_marker(line: str) -> bool:
83
+ """Check if line has ignore-end comment marker.
84
+
85
+ Only matches actual comment lines (starting with # or //), not strings
86
+ containing the marker text.
87
+
88
+ Args:
89
+ line: Line of code to check
90
+
91
+ Returns:
92
+ True if line is a proper ignore-end comment
93
+ """
94
+ stripped = line.strip().lower()
95
+ if not (stripped.startswith("#") or stripped.startswith("//")):
96
+ return False
97
+ return "ignore-end" in stripped and ("thailint:" in stripped or "design-lint:" in stripped)
98
+
99
+
100
+ def check_general_ignore(line: str) -> bool:
101
+ """Check if line has general ignore directive (no specific rules).
102
+
103
+ Args:
104
+ line: Line containing ignore directive
105
+
106
+ Returns:
107
+ True if no specific rules are specified (not bracket syntax)
108
+ """
109
+ return "ignore-file[" not in line