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
@@ -0,0 +1,189 @@
1
+ """
2
+ Purpose: Configuration dataclass for stringly-typed linter
3
+
4
+ Scope: Define configurable options for stringly-typed pattern detection
5
+
6
+ Overview: Provides StringlyTypedConfig for customizing linter behavior including minimum
7
+ occurrences required to flag patterns, enum value thresholds, cross-file detection
8
+ settings, and ignore patterns. The stringly-typed linter detects code patterns where
9
+ plain strings are used instead of proper enums or typed alternatives. Integrates with
10
+ the orchestrator's configuration system to allow users to customize detection via
11
+ .thailint.yaml configuration files. Follows the same configuration pattern as other
12
+ thai-lint linters.
13
+
14
+ Dependencies: dataclasses, typing
15
+
16
+ Exports: StringlyTypedConfig dataclass, default constants
17
+
18
+ Interfaces: StringlyTypedConfig.from_dict() class method for configuration loading
19
+
20
+ Implementation: Dataclass with sensible defaults, validation in __post_init__, and config
21
+ loading from dictionary with language-specific override support
22
+
23
+ Suppressions:
24
+ - too-many-instance-attributes: Configuration dataclass with cohesive detection settings
25
+ """
26
+
27
+ from dataclasses import dataclass, field
28
+ from typing import Any
29
+
30
+ # Default thresholds
31
+ DEFAULT_MIN_OCCURRENCES = 2
32
+ DEFAULT_MIN_VALUES_FOR_ENUM = 2
33
+ DEFAULT_MAX_VALUES_FOR_ENUM = 6
34
+
35
+ # Default ignore patterns - test directories are excluded by default
36
+ # because test fixtures commonly use string literals for mocking
37
+ DEFAULT_IGNORE_PATTERNS: list[str] = [
38
+ "**/tests/**",
39
+ "**/test/**",
40
+ "**/*_test.py",
41
+ "**/*_test.ts",
42
+ "**/*.test.ts",
43
+ "**/*.test.tsx",
44
+ "**/*.spec.ts",
45
+ "**/*.spec.tsx",
46
+ "**/*.stories.ts",
47
+ "**/*.stories.tsx",
48
+ "**/conftest.py",
49
+ "**/fixtures/**",
50
+ ]
51
+
52
+
53
+ @dataclass
54
+ class StringlyTypedConfig: # pylint: disable=too-many-instance-attributes
55
+ """Configuration for stringly-typed linter.
56
+
57
+ Note: Pylint too-many-instance-attributes disabled. This is a configuration
58
+ dataclass serving as a data container for related stringly-typed linter settings.
59
+ All 8 attributes are cohesively related (detection thresholds, filtering options,
60
+ cross-file settings, exclusion patterns). Splitting would reduce cohesion and make
61
+ configuration loading more complex without meaningful benefit. This follows the
62
+ established pattern in DRYConfig.
63
+ """
64
+
65
+ enabled: bool = True
66
+ """Whether the linter is enabled."""
67
+
68
+ min_occurrences: int = DEFAULT_MIN_OCCURRENCES
69
+ """Minimum number of cross-file occurrences required to flag a violation."""
70
+
71
+ min_values_for_enum: int = DEFAULT_MIN_VALUES_FOR_ENUM
72
+ """Minimum number of unique string values to suggest an enum."""
73
+
74
+ max_values_for_enum: int = DEFAULT_MAX_VALUES_FOR_ENUM
75
+ """Maximum number of unique string values to suggest an enum (above this, not enum-worthy)."""
76
+
77
+ require_cross_file: bool = True
78
+ """Whether to require cross-file occurrences to flag violations."""
79
+
80
+ ignore: list[str] = field(default_factory=list)
81
+ """File patterns to ignore. Defaults merged with test directories in from_dict."""
82
+
83
+ allowed_string_sets: list[list[str]] = field(default_factory=list)
84
+ """String sets that are allowed and should not be flagged."""
85
+
86
+ exclude_variables: list[str] = field(default_factory=list)
87
+ """Variable names to exclude from detection."""
88
+
89
+ def __post_init__(self) -> None:
90
+ """Validate configuration values."""
91
+ if self.min_occurrences < 1:
92
+ raise ValueError(f"min_occurrences must be at least 1, got {self.min_occurrences}")
93
+ if self.min_values_for_enum < 2:
94
+ raise ValueError(
95
+ f"min_values_for_enum must be at least 2, got {self.min_values_for_enum}"
96
+ )
97
+ if self.max_values_for_enum < self.min_values_for_enum:
98
+ raise ValueError(
99
+ f"max_values_for_enum ({self.max_values_for_enum}) must be >= "
100
+ f"min_values_for_enum ({self.min_values_for_enum})"
101
+ )
102
+
103
+ @classmethod
104
+ def from_dict(
105
+ cls, config: dict[str, Any], language: str | None = None
106
+ ) -> "StringlyTypedConfig":
107
+ """Load configuration from dictionary.
108
+
109
+ Args:
110
+ config: Dictionary containing configuration values
111
+ language: Programming language for language-specific overrides
112
+
113
+ Returns:
114
+ StringlyTypedConfig instance with values from dictionary
115
+ """
116
+ # Check for language-specific overrides first
117
+ if language and language in config:
118
+ lang_config = config[language]
119
+ return cls._from_merged_config(config, lang_config)
120
+
121
+ return cls._from_base_config(config)
122
+
123
+ @classmethod
124
+ def _from_base_config(cls, config: dict[str, Any]) -> "StringlyTypedConfig":
125
+ """Create config from base configuration dictionary.
126
+
127
+ Args:
128
+ config: Base configuration dictionary
129
+
130
+ Returns:
131
+ StringlyTypedConfig instance
132
+ """
133
+ # Merge user ignore patterns with defaults
134
+ user_ignore = config.get("ignore", [])
135
+ merged_ignore = DEFAULT_IGNORE_PATTERNS.copy() + user_ignore
136
+
137
+ return cls(
138
+ enabled=config.get("enabled", True),
139
+ min_occurrences=config.get("min_occurrences", DEFAULT_MIN_OCCURRENCES),
140
+ min_values_for_enum=config.get("min_values_for_enum", DEFAULT_MIN_VALUES_FOR_ENUM),
141
+ max_values_for_enum=config.get("max_values_for_enum", DEFAULT_MAX_VALUES_FOR_ENUM),
142
+ require_cross_file=config.get("require_cross_file", True),
143
+ ignore=merged_ignore,
144
+ allowed_string_sets=config.get("allowed_string_sets", []),
145
+ exclude_variables=config.get("exclude_variables", []),
146
+ )
147
+
148
+ @classmethod
149
+ def _from_merged_config(
150
+ cls, base_config: dict[str, Any], lang_config: dict[str, Any]
151
+ ) -> "StringlyTypedConfig":
152
+ """Create config with language-specific overrides merged.
153
+
154
+ Args:
155
+ base_config: Base configuration dictionary
156
+ lang_config: Language-specific configuration overrides
157
+
158
+ Returns:
159
+ StringlyTypedConfig instance with merged values
160
+ """
161
+ # Merge user ignore patterns with defaults
162
+ user_ignore = lang_config.get("ignore", base_config.get("ignore", []))
163
+ merged_ignore = DEFAULT_IGNORE_PATTERNS.copy() + user_ignore
164
+
165
+ return cls(
166
+ enabled=lang_config.get("enabled", base_config.get("enabled", True)),
167
+ min_occurrences=lang_config.get(
168
+ "min_occurrences",
169
+ base_config.get("min_occurrences", DEFAULT_MIN_OCCURRENCES),
170
+ ),
171
+ min_values_for_enum=lang_config.get(
172
+ "min_values_for_enum",
173
+ base_config.get("min_values_for_enum", DEFAULT_MIN_VALUES_FOR_ENUM),
174
+ ),
175
+ max_values_for_enum=lang_config.get(
176
+ "max_values_for_enum",
177
+ base_config.get("max_values_for_enum", DEFAULT_MAX_VALUES_FOR_ENUM),
178
+ ),
179
+ require_cross_file=lang_config.get(
180
+ "require_cross_file", base_config.get("require_cross_file", True)
181
+ ),
182
+ ignore=merged_ignore,
183
+ allowed_string_sets=lang_config.get(
184
+ "allowed_string_sets", base_config.get("allowed_string_sets", [])
185
+ ),
186
+ exclude_variables=lang_config.get(
187
+ "exclude_variables", base_config.get("exclude_variables", [])
188
+ ),
189
+ )
@@ -0,0 +1,451 @@
1
+ """
2
+ Purpose: Context-aware filtering for stringly-typed function call violations
3
+
4
+ Scope: Filter out false positive function call patterns based on function names and contexts
5
+
6
+ Overview: Implements a blocklist-based filtering approach to reduce false positives in the
7
+ stringly-typed linter's function call detection. Excludes known false positive patterns
8
+ including dictionary access methods, string processing functions, logging calls, framework
9
+ validators, and external API functions. Uses function name pattern matching and parameter
10
+ position filtering to achieve <5% false positive rate.
11
+
12
+ Dependencies: re module for pattern matching
13
+
14
+ Exports: should_include, are_all_values_excluded functions
15
+
16
+ Interfaces: should_include(function_name, param_index, string_values) -> bool,
17
+ are_all_values_excluded(unique_values) -> bool
18
+
19
+ Implementation: Blocklist-based filtering with function name patterns, parameter position rules,
20
+ and string value pattern detection
21
+ """
22
+
23
+ import re
24
+
25
+ # Function name suffixes that always indicate false positives
26
+ _EXCLUDED_FUNCTION_SUFFIXES: tuple[str, ...] = (
27
+ # Exception constructors - error messages are inherently unique strings
28
+ "Error",
29
+ "Exception",
30
+ "Warning",
31
+ )
32
+
33
+ # Function name patterns to always exclude (case-insensitive suffix/contains match)
34
+ _EXCLUDED_FUNCTION_PATTERNS: tuple[str, ...] = (
35
+ # Dictionary/object access - these are metadata access, not domain values
36
+ ".get",
37
+ ".set",
38
+ ".pop",
39
+ ".setdefault",
40
+ ".update",
41
+ # List/collection operations
42
+ ".append",
43
+ ".extend",
44
+ ".insert",
45
+ ".add",
46
+ ".remove",
47
+ ".push",
48
+ ".set",
49
+ ".has",
50
+ "hasItem",
51
+ "push",
52
+ # String processing - delimiters and format strings
53
+ # Note: both .method and method forms to catch both method calls and standalone functions
54
+ ".split",
55
+ "split",
56
+ ".rsplit",
57
+ ".replace",
58
+ "replace",
59
+ ".strip",
60
+ ".rstrip",
61
+ ".lstrip",
62
+ ".startswith",
63
+ "startswith",
64
+ ".startsWith",
65
+ "startsWith",
66
+ ".endswith",
67
+ "endswith",
68
+ ".endsWith",
69
+ "endsWith",
70
+ ".includes",
71
+ "includes",
72
+ ".indexOf",
73
+ "indexOf",
74
+ ".lastIndexOf",
75
+ ".match",
76
+ ".search",
77
+ ".format",
78
+ ".join",
79
+ "join",
80
+ ".encode",
81
+ ".decode",
82
+ ".lower",
83
+ ".upper",
84
+ ".trim",
85
+ ".trimStart",
86
+ ".trimEnd",
87
+ ".padStart",
88
+ ".padEnd",
89
+ "strftime",
90
+ "strptime",
91
+ # Logging and output - human-readable messages
92
+ "logger.debug",
93
+ "logger.info",
94
+ "logger.warning",
95
+ "logger.error",
96
+ "logger.critical",
97
+ "logger.exception",
98
+ "logging.debug",
99
+ "logging.info",
100
+ "logging.warning",
101
+ "logging.error",
102
+ "print",
103
+ "echo",
104
+ "console.print",
105
+ "console.log",
106
+ "typer.echo",
107
+ "click.echo",
108
+ # Regex - pattern strings
109
+ "re.sub",
110
+ "re.match",
111
+ "re.search",
112
+ "re.compile",
113
+ "re.findall",
114
+ "re.split",
115
+ # Environment variables
116
+ "os.environ.get",
117
+ "os.getenv",
118
+ "environ.get",
119
+ "getenv",
120
+ # File operations
121
+ "open",
122
+ "Path",
123
+ # Framework validators - must be strings matching field names
124
+ "field_validator",
125
+ "validator",
126
+ "computed_field",
127
+ # Type system - required Python syntax
128
+ "TypeVar",
129
+ "Generic",
130
+ "cast",
131
+ # Numeric - string representations of numbers
132
+ "Decimal",
133
+ "int",
134
+ "float",
135
+ # Exception constructors - error messages
136
+ "ValueError",
137
+ "TypeError",
138
+ "KeyError",
139
+ "AttributeError",
140
+ "RuntimeError",
141
+ "Exception",
142
+ "raise",
143
+ "APIException",
144
+ "HTTPException",
145
+ "ValidationError",
146
+ # CLI frameworks - short flags, option names, prompts
147
+ "typer.Option",
148
+ "typer.Argument",
149
+ "typer.confirm",
150
+ "typer.prompt",
151
+ "click.option",
152
+ "click.argument",
153
+ "click.confirm",
154
+ "click.prompt",
155
+ ".command",
156
+ # HTTP/API clients - external protocol strings
157
+ "requests.get",
158
+ "requests.post",
159
+ "requests.put",
160
+ "requests.delete",
161
+ "requests.patch",
162
+ "httpx.get",
163
+ "httpx.post",
164
+ "axios.get",
165
+ "axios.post",
166
+ "axios.put",
167
+ "axios.delete",
168
+ "axios.patch",
169
+ "client.get",
170
+ "client.post",
171
+ "client.put",
172
+ "client.delete",
173
+ "session.client",
174
+ "session.resource",
175
+ "_request",
176
+ # Browser/DOM APIs - CSS selectors and data URLs
177
+ "document.querySelector",
178
+ "document.querySelectorAll",
179
+ "document.getElementById",
180
+ "document.getElementsByClassName",
181
+ "canvas.toDataURL",
182
+ "canvas.toBlob",
183
+ "createElement",
184
+ "getAttribute",
185
+ "setAttribute",
186
+ "addEventListener",
187
+ "removeEventListener",
188
+ "localStorage.getItem",
189
+ "localStorage.setItem",
190
+ "sessionStorage.getItem",
191
+ "sessionStorage.setItem",
192
+ "window.confirm",
193
+ "window.alert",
194
+ "window.prompt",
195
+ "confirm",
196
+ "alert",
197
+ "prompt",
198
+ # React hooks - internal state identifiers
199
+ "useRef",
200
+ "useState",
201
+ "useCallback",
202
+ "useMemo",
203
+ # AWS SDK - service names and API parameters
204
+ "boto3.client",
205
+ "boto3.resource",
206
+ "generate_presigned_url",
207
+ # AWS CDK - infrastructure as code (broad patterns)
208
+ "s3.",
209
+ "ec2.",
210
+ "logs.",
211
+ "route53.",
212
+ "lambda_.",
213
+ "_lambda.",
214
+ "tasks.",
215
+ "iam.",
216
+ "dynamodb.",
217
+ "sqs.",
218
+ "sns.",
219
+ "apigateway.",
220
+ "cloudfront.",
221
+ "cdk.",
222
+ "sfn.",
223
+ "acm.",
224
+ "cloudwatch.",
225
+ "secretsmanager.",
226
+ "cr.",
227
+ "pipes.",
228
+ "rds.",
229
+ "elasticache.",
230
+ "from_lookup",
231
+ "generate_resource_name",
232
+ "CfnPipe",
233
+ "CfnOutput",
234
+ # FastAPI/Starlette routing
235
+ "router.get",
236
+ "router.post",
237
+ "router.put",
238
+ "router.delete",
239
+ "router.patch",
240
+ "app.get",
241
+ "app.post",
242
+ "app.put",
243
+ "app.delete",
244
+ "@app.",
245
+ "@router.",
246
+ # DynamoDB attribute access
247
+ "Key",
248
+ "Attr",
249
+ "ConditionExpression",
250
+ # Azure CLI - external tool invocation
251
+ "az",
252
+ # Database/ORM - schema definitions
253
+ "op.add_column",
254
+ "op.drop_column",
255
+ "op.create_table",
256
+ "op.alter_column",
257
+ "sa.Column",
258
+ "sa.PrimaryKeyConstraint",
259
+ "sa.ForeignKeyConstraint",
260
+ "Column",
261
+ "relationship",
262
+ "postgresql.ENUM",
263
+ "ENUM",
264
+ # Python built-ins
265
+ "getattr",
266
+ "setattr",
267
+ "hasattr",
268
+ "delattr",
269
+ "isinstance",
270
+ "issubclass",
271
+ # Pydantic/dataclass fields
272
+ "Field",
273
+ "PrivateAttr",
274
+ # UI frameworks - display text
275
+ "QLabel",
276
+ "QPushButton",
277
+ "QMessageBox",
278
+ "QCheckBox",
279
+ "setWindowTitle",
280
+ "setText",
281
+ "setToolTip",
282
+ "setPlaceholderText",
283
+ "setStatusTip",
284
+ "Static",
285
+ "Label",
286
+ "Button",
287
+ # Table/grid display - formatting
288
+ "table.add_row",
289
+ "add_row",
290
+ "add_column",
291
+ "Table",
292
+ "Panel",
293
+ "Console",
294
+ # Testing - mocks and fixtures
295
+ "monkeypatch.setattr",
296
+ "patch",
297
+ "Mock",
298
+ "MagicMock",
299
+ "PropertyMock",
300
+ # String parsing methods - internal message processing
301
+ ".index",
302
+ ".find",
303
+ ".rfind",
304
+ ".rindex",
305
+ # Storybook - action handlers
306
+ "action",
307
+ "fn",
308
+ # React state setters - UI state names
309
+ "setMessage",
310
+ "setError",
311
+ "setLoading",
312
+ "setStatus",
313
+ "setText",
314
+ # API clients - external endpoints
315
+ "API.",
316
+ "api.",
317
+ # CSS/styling
318
+ "setStyleSheet",
319
+ "add_class",
320
+ "remove_class",
321
+ # JSON/serialization - output identifiers
322
+ "_output",
323
+ "json.dumps",
324
+ "json.loads",
325
+ # Health checks - framework pattern
326
+ "register_health_check",
327
+ # Config management - dynamic config keys
328
+ "ensure_config_section",
329
+ "set_config_value",
330
+ "get_config_value",
331
+ )
332
+
333
+ # Function names where second parameter (index 1) should be excluded
334
+ # These are typically default values, not keys
335
+ _EXCLUDE_PARAM_INDEX_1: tuple[str, ...] = (
336
+ ".get",
337
+ "os.environ.get",
338
+ "environ.get",
339
+ "getattr",
340
+ "os.getenv",
341
+ "getenv",
342
+ )
343
+
344
+ # String value patterns that indicate false positives
345
+ _EXCLUDED_VALUE_PATTERNS: tuple[re.Pattern[str], ...] = (
346
+ # strftime format strings
347
+ re.compile(r"^%[A-Za-z%-]+$"),
348
+ # Single character delimiters
349
+ re.compile(r"^[\n\t\r,;:|/\\.\-_]$"),
350
+ # Empty string or whitespace only
351
+ re.compile(r"^\s*$"),
352
+ # HTTP methods (external protocol)
353
+ re.compile(r"^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$"),
354
+ # Numeric strings (should use Decimal or int)
355
+ re.compile(r"^-?\d+\.?\d*$"),
356
+ # Short CLI flags
357
+ re.compile(r"^-[a-zA-Z]$"),
358
+ # CSS/Rich markup
359
+ re.compile(r"^\[/?[a-z]+\]"),
360
+ # File modes (only multi-char modes to avoid false positives on single letters)
361
+ re.compile(r"^[rwa][bt]\+?$|^[rwa]\+$"),
362
+ )
363
+
364
+
365
+ def should_include(
366
+ function_name: str,
367
+ param_index: int,
368
+ unique_values: set[str],
369
+ ) -> bool:
370
+ """Determine if a function call pattern should be included in violations.
371
+
372
+ Args:
373
+ function_name: Name of the function being called
374
+ param_index: Index of the parameter (0-based)
375
+ unique_values: Set of unique string values passed to this parameter
376
+
377
+ Returns:
378
+ True if this pattern should generate a violation, False to filter it out
379
+ """
380
+ # Check function name patterns
381
+ if _is_excluded_function(function_name):
382
+ return False
383
+
384
+ # Check parameter position for specific functions
385
+ if _is_excluded_param_position(function_name, param_index):
386
+ return False
387
+
388
+ # Check if all values match excluded patterns
389
+ if _all_values_excluded(unique_values):
390
+ return False
391
+
392
+ return True
393
+
394
+
395
+ def are_all_values_excluded(unique_values: set[str]) -> bool:
396
+ """Check if all values match excluded patterns (numeric strings, delimiters, etc.).
397
+
398
+ Public interface for value-based filtering used by violation generator.
399
+
400
+ Args:
401
+ unique_values: Set of unique string values to check
402
+
403
+ Returns:
404
+ True if all values match excluded patterns, False otherwise
405
+ """
406
+ return _all_values_excluded(unique_values)
407
+
408
+
409
+ def _is_excluded_function(function_name: str) -> bool:
410
+ """Check if function name matches any excluded pattern."""
411
+ # Check suffix patterns (e.g., *Error, *Exception)
412
+ if _matches_suffix(function_name):
413
+ return True
414
+ return _matches_pattern(function_name.lower())
415
+
416
+
417
+ def _matches_suffix(function_name: str) -> bool:
418
+ """Check if function name ends with an excluded suffix."""
419
+ return any(function_name.endswith(s) for s in _EXCLUDED_FUNCTION_SUFFIXES)
420
+
421
+
422
+ def _matches_pattern(func_lower: str) -> bool:
423
+ """Check if function name matches any excluded pattern."""
424
+ return any(
425
+ pattern.lower() in func_lower or func_lower.endswith(pattern.lower())
426
+ for pattern in _EXCLUDED_FUNCTION_PATTERNS
427
+ )
428
+
429
+
430
+ def _is_excluded_param_position(function_name: str, param_index: int) -> bool:
431
+ """Check if this parameter position should be excluded for this function."""
432
+ if param_index != 1:
433
+ return False
434
+
435
+ func_lower = function_name.lower()
436
+ return any(
437
+ pattern.lower() in func_lower or func_lower.endswith(pattern.lower())
438
+ for pattern in _EXCLUDE_PARAM_INDEX_1
439
+ )
440
+
441
+
442
+ def _all_values_excluded(unique_values: set[str]) -> bool:
443
+ """Check if all values in the set match excluded patterns."""
444
+ if not unique_values:
445
+ return True
446
+ return all(_is_excluded_value(value) for value in unique_values)
447
+
448
+
449
+ def _is_excluded_value(value: str) -> bool:
450
+ """Check if a single value matches any excluded pattern."""
451
+ return any(pattern.match(value) for pattern in _EXCLUDED_VALUE_PATTERNS)