pyrefactor 1.0.4__tar.gz → 1.0.6__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 (49) hide show
  1. {pyrefactor-1.0.4/src/pyrefactor.egg-info → pyrefactor-1.0.6}/PKG-INFO +1 -1
  2. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/__init__.py +1 -1
  3. {pyrefactor-1.0.4 → pyrefactor-1.0.6/src/pyrefactor.egg-info}/PKG-INFO +1 -1
  4. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor.egg-info/SOURCES.txt +0 -5
  5. pyrefactor-1.0.4/tests/test_hypothesis_analyzer.py +0 -499
  6. pyrefactor-1.0.4/tests/test_hypothesis_ast_visitor.py +0 -563
  7. pyrefactor-1.0.4/tests/test_hypothesis_config.py +0 -382
  8. pyrefactor-1.0.4/tests/test_hypothesis_models.py +0 -429
  9. pyrefactor-1.0.4/tests/test_hypothesis_reporter.py +0 -526
  10. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/LICENSE.md +0 -0
  11. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/README.md +0 -0
  12. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/pyproject.toml +0 -0
  13. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/setup.cfg +0 -0
  14. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/__main__.py +0 -0
  15. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/analyzer.py +0 -0
  16. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/ast_visitor.py +0 -0
  17. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/config.py +0 -0
  18. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/__init__.py +0 -0
  19. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/boolean_logic.py +0 -0
  20. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/comparisons.py +0 -0
  21. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/complexity.py +0 -0
  22. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/context_manager.py +0 -0
  23. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/control_flow.py +0 -0
  24. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/dict_operations.py +0 -0
  25. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/duplication.py +0 -0
  26. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/loops.py +0 -0
  27. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/detectors/performance.py +0 -0
  28. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/models.py +0 -0
  29. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/py.typed +0 -0
  30. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor/reporter.py +0 -0
  31. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor.egg-info/dependency_links.txt +0 -0
  32. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor.egg-info/entry_points.txt +0 -0
  33. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor.egg-info/requires.txt +0 -0
  34. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/src/pyrefactor.egg-info/top_level.txt +0 -0
  35. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_analyzer.py +0 -0
  36. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_boolean_logic_detector.py +0 -0
  37. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_cli.py +0 -0
  38. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_comparisons_detector.py +0 -0
  39. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_complexity_detector.py +0 -0
  40. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_config.py +0 -0
  41. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_context_manager_detector.py +0 -0
  42. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_control_flow_detector.py +0 -0
  43. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_dict_operations_detector.py +0 -0
  44. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_duplication_detector.py +0 -0
  45. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_integration.py +0 -0
  46. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_loops_detector.py +0 -0
  47. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_models.py +0 -0
  48. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_performance_detector.py +0 -0
  49. {pyrefactor-1.0.4 → pyrefactor-1.0.6}/tests/test_reporter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrefactor
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A Python refactoring and optimization linter that analyzes code for performance issues, complexity problems, and opportunities for improvement
5
5
  Author: tboy1337
6
6
  Maintainer: tboy1337
@@ -1,3 +1,3 @@
1
1
  """PyRefactor - A Python refactoring and optimization linter."""
2
2
 
3
- __version__ = "1.0.4"
3
+ __version__ = "1.0.6"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrefactor
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: A Python refactoring and optimization linter that analyzes code for performance issues, complexity problems, and opportunities for improvement
5
5
  Author: tboy1337
6
6
  Maintainer: tboy1337
@@ -35,11 +35,6 @@ tests/test_context_manager_detector.py
35
35
  tests/test_control_flow_detector.py
36
36
  tests/test_dict_operations_detector.py
37
37
  tests/test_duplication_detector.py
38
- tests/test_hypothesis_analyzer.py
39
- tests/test_hypothesis_ast_visitor.py
40
- tests/test_hypothesis_config.py
41
- tests/test_hypothesis_models.py
42
- tests/test_hypothesis_reporter.py
43
38
  tests/test_integration.py
44
39
  tests/test_loops_detector.py
45
40
  tests/test_models.py
@@ -1,499 +0,0 @@
1
- """Property-based tests for Analyzer using Hypothesis."""
2
-
3
- import tempfile
4
- from pathlib import Path
5
-
6
- from hypothesis import assume, given, settings
7
- from hypothesis import strategies as st
8
-
9
- from pyrefactor.analyzer import Analyzer
10
- from pyrefactor.config import ComplexityConfig, Config
11
-
12
-
13
- # Strategies for file paths and patterns
14
- @st.composite
15
- def file_path_strategy(draw: st.DrawFn) -> str:
16
- """Generate a file path string."""
17
- parts = draw(
18
- st.lists(
19
- st.text(
20
- min_size=1,
21
- max_size=20,
22
- alphabet=st.characters(
23
- whitelist_categories=("Ll", "Lu", "Nd"),
24
- min_codepoint=48,
25
- max_codepoint=122,
26
- ).filter(lambda c: c not in r'\/:*?"<>|'),
27
- ),
28
- min_size=1,
29
- max_size=3,
30
- )
31
- )
32
- return "/".join(parts) + ".py"
33
-
34
-
35
- @st.composite
36
- def exclude_pattern_strategy(draw: st.DrawFn) -> str:
37
- """Generate an exclusion pattern."""
38
- return draw(
39
- st.text(
40
- min_size=1,
41
- max_size=30,
42
- alphabet=st.characters(whitelist_categories=("Ll", "Lu", "Nd", "P")),
43
- )
44
- )
45
-
46
-
47
- @st.composite
48
- def valid_python_code_strategy(draw: st.DrawFn) -> str:
49
- """Generate valid Python code snippets."""
50
- func_name = draw(
51
- st.text(
52
- min_size=1,
53
- max_size=15,
54
- alphabet=st.characters(
55
- whitelist_categories=("Ll",), min_codepoint=97, max_codepoint=122
56
- ),
57
- )
58
- )
59
- num_lines = draw(st.integers(min_value=1, max_value=20))
60
- return f'def {func_name}():\n{chr(10).join(f" x{i} = {i}" for i in range(num_lines))}'
61
-
62
-
63
- class TestAnalyzerFileFilteringProperties:
64
- """Property-based tests for file filtering logic."""
65
-
66
- @given(
67
- st.lists(
68
- st.text(
69
- min_size=1,
70
- max_size=50,
71
- alphabet=st.characters(whitelist_categories=("Ll", "Lu", "Nd", "P")),
72
- ),
73
- max_size=5,
74
- )
75
- )
76
- def test_filter_preserves_empty_list(self, exclude_patterns: list[str]) -> None:
77
- """Property: Filtering an empty file list returns empty list."""
78
- config = Config(exclude_patterns=exclude_patterns)
79
- analyzer = Analyzer(config)
80
- result = analyzer._filter_excluded_files([])
81
- assert not result
82
-
83
- @given(st.lists(exclude_pattern_strategy(), max_size=5))
84
- def test_filter_with_no_patterns_returns_all(self, patterns: list[str]) -> None:
85
- """Property: With no exclusion patterns, all files pass through."""
86
- config = Config(exclude_patterns=[])
87
- analyzer = Analyzer(config)
88
-
89
- # Create some test paths
90
- test_paths = [
91
- Path("test1.py"),
92
- Path("test2.py"),
93
- Path("subdir/test3.py"),
94
- ]
95
-
96
- result = analyzer._filter_excluded_files(test_paths)
97
- assert len(result) == len(test_paths)
98
-
99
- @given(
100
- st.text(
101
- min_size=1,
102
- max_size=20,
103
- alphabet=st.characters(whitelist_categories=("Ll",)),
104
- )
105
- )
106
- def test_filter_excludes_matching_pattern(self, pattern: str) -> None:
107
- """Property: Files matching exclusion pattern are filtered out."""
108
- config = Config(exclude_patterns=[pattern])
109
- analyzer = Analyzer(config)
110
-
111
- # Create paths: some with pattern, some without
112
- matching_path = Path(f"dir/{pattern}/test.py")
113
- non_matching_path = Path("other/test.py")
114
-
115
- result = analyzer._filter_excluded_files([matching_path, non_matching_path])
116
-
117
- # Matching path should be excluded
118
- assert matching_path not in result
119
- # Non-matching path should remain if pattern isn't in it
120
- if pattern not in str(non_matching_path):
121
- assert non_matching_path in result
122
-
123
- @given(st.lists(exclude_pattern_strategy(), min_size=1, max_size=5))
124
- def test_filter_result_subset_of_input(self, patterns: list[str]) -> None:
125
- """Property: Filtered result is always a subset of input."""
126
- config = Config(exclude_patterns=patterns)
127
- analyzer = Analyzer(config)
128
-
129
- test_paths = [
130
- Path("test1.py"),
131
- Path("test2.py"),
132
- Path("excluded/test.py"),
133
- ]
134
-
135
- result = analyzer._filter_excluded_files(test_paths)
136
-
137
- # Result should be a subset
138
- assert len(result) <= len(test_paths)
139
- assert all(path in test_paths for path in result)
140
-
141
-
142
- class TestAnalyzerBasicProperties:
143
- """Property-based tests for basic analyzer behavior."""
144
-
145
- @given(st.integers(min_value=1, max_value=100))
146
- def test_analyzer_accepts_any_positive_config_values(
147
- self, max_branches: int
148
- ) -> None:
149
- """Property: Analyzer can be initialized with any valid config."""
150
- config = Config(complexity=ComplexityConfig(max_branches=max_branches))
151
- analyzer = Analyzer(config)
152
- assert analyzer.config.complexity.max_branches == max_branches
153
-
154
- def test_analyzer_preserves_config(self) -> None:
155
- """Property: Analyzer preserves the config it's initialized with."""
156
- config = Config(
157
- complexity=ComplexityConfig(max_branches=15, max_nesting_depth=5)
158
- )
159
- analyzer = Analyzer(config)
160
-
161
- assert analyzer.config.complexity.max_branches == 15
162
- assert analyzer.config.complexity.max_nesting_depth == 5
163
-
164
-
165
- class TestAnalyzerFileAnalysisProperties:
166
- """Property-based tests for file analysis."""
167
-
168
- @given(valid_python_code_strategy())
169
- @settings(max_examples=1000, deadline=None)
170
- def test_analyze_file_returns_file_analysis(self, code: str) -> None:
171
- """Property: Analyzing a file always returns a FileAnalysis object."""
172
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
173
- f.write(code)
174
- temp_path = Path(f.name)
175
-
176
- try:
177
- config = Config()
178
- analyzer = Analyzer(config)
179
- result = analyzer.analyze_file(temp_path)
180
-
181
- assert result.file_path == str(temp_path)
182
- assert isinstance(result.issues, list)
183
- assert result.lines_of_code >= 0
184
- finally:
185
- temp_path.unlink()
186
-
187
- @given(st.integers(min_value=1, max_value=50))
188
- @settings(max_examples=1000, deadline=None)
189
- def test_analyze_file_counts_lines_correctly(self, num_lines: int) -> None:
190
- """Property: Analyzer correctly counts lines of code."""
191
- code = "\n".join(f"x{i} = {i}" for i in range(num_lines))
192
-
193
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
194
- f.write(code)
195
- temp_path = Path(f.name)
196
-
197
- try:
198
- config = Config()
199
- analyzer = Analyzer(config)
200
- result = analyzer.analyze_file(temp_path)
201
-
202
- # Should count the number of lines
203
- assert result.lines_of_code == num_lines
204
- finally:
205
- temp_path.unlink()
206
-
207
- def test_analyze_file_with_syntax_error_sets_parse_error(self) -> None:
208
- """Property: Files with syntax errors have parse_error set."""
209
- invalid_code = "def broken syntax here"
210
-
211
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
212
- f.write(invalid_code)
213
- temp_path = Path(f.name)
214
-
215
- try:
216
- config = Config()
217
- analyzer = Analyzer(config)
218
- result = analyzer.analyze_file(temp_path)
219
-
220
- assert result.parse_error is not None
221
- assert "Syntax error" in result.parse_error
222
- finally:
223
- temp_path.unlink()
224
-
225
- def test_analyze_empty_file_succeeds(self) -> None:
226
- """Property: Analyzing an empty file doesn't crash."""
227
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
228
- f.write("")
229
- temp_path = Path(f.name)
230
-
231
- try:
232
- config = Config()
233
- analyzer = Analyzer(config)
234
- result = analyzer.analyze_file(temp_path)
235
-
236
- assert result.lines_of_code == 0
237
- # Empty file should parse successfully
238
- assert result.parse_error is None or result.parse_error == ""
239
- finally:
240
- temp_path.unlink()
241
-
242
-
243
- class TestAnalyzerMultipleFilesProperties:
244
- """Property-based tests for analyzing multiple files."""
245
-
246
- @given(st.integers(min_value=1, max_value=5))
247
- @settings(max_examples=1000, deadline=None)
248
- def test_analyze_files_processes_all_files(self, num_files: int) -> None:
249
- """Property: analyze_files processes all provided files."""
250
- temp_files = []
251
- temp_dir = tempfile.mkdtemp()
252
-
253
- try:
254
- for i in range(num_files):
255
- temp_path = Path(temp_dir) / f"test{i}.py"
256
- temp_path.write_text(f"def func{i}(): pass")
257
- temp_files.append(temp_path)
258
-
259
- config = Config()
260
- analyzer = Analyzer(config)
261
- result = analyzer.analyze_files(temp_files)
262
-
263
- # Should analyze all files
264
- assert result.files_analyzed() == num_files
265
- finally:
266
- for temp_file in temp_files:
267
- temp_file.unlink()
268
- Path(temp_dir).rmdir()
269
-
270
- def test_analyze_directory_finds_python_files(self) -> None:
271
- """Property: analyze_directory finds all .py files."""
272
- temp_dir = Path(tempfile.mkdtemp())
273
-
274
- try:
275
- # Create some Python files
276
- (temp_dir / "test1.py").write_text("def func1(): pass")
277
- (temp_dir / "test2.py").write_text("def func2(): pass")
278
- (temp_dir / "readme.txt").write_text("Not Python")
279
-
280
- config = Config()
281
- analyzer = Analyzer(config)
282
- result = analyzer.analyze_directory(temp_dir)
283
-
284
- # Should find 2 Python files, not the .txt
285
- assert result.files_analyzed() == 2
286
- finally:
287
- for file in temp_dir.iterdir():
288
- file.unlink()
289
- temp_dir.rmdir()
290
-
291
-
292
- class TestAnalyzerResultAggregationProperties:
293
- """Property-based tests for result aggregation."""
294
-
295
- @given(st.integers(min_value=0, max_value=5))
296
- @settings(max_examples=1000, deadline=None)
297
- def test_result_aggregates_issues_from_all_files(self, num_files: int) -> None:
298
- """Property: Result contains all issues from all analyzed files."""
299
- assume(num_files > 0)
300
-
301
- temp_dir = Path(tempfile.mkdtemp())
302
-
303
- try:
304
- for i in range(num_files):
305
- # Create code with varying complexity
306
- code = f"""
307
- def complex_func_{i}():
308
- if x:
309
- if y:
310
- if z:
311
- if w:
312
- pass
313
- """
314
- (temp_dir / f"test{i}.py").write_text(code)
315
-
316
- config = Config(complexity=ComplexityConfig(max_nesting_depth=2))
317
- analyzer = Analyzer(config)
318
- result = analyzer.analyze_directory(temp_dir)
319
-
320
- # Should have analyzed all files
321
- assert result.files_analyzed() == num_files
322
-
323
- # Total issues should be sum of issues from all files
324
- total_from_files = sum(len(fa.issues) for fa in result.file_analyses)
325
- assert result.total_issues() == total_from_files
326
- finally:
327
- for file in temp_dir.iterdir():
328
- file.unlink()
329
- temp_dir.rmdir()
330
-
331
- def test_empty_directory_returns_empty_result(self) -> None:
332
- """Property: Analyzing empty directory returns result with 0 files."""
333
- temp_dir = Path(tempfile.mkdtemp())
334
-
335
- try:
336
- config = Config()
337
- analyzer = Analyzer(config)
338
- result = analyzer.analyze_directory(temp_dir)
339
-
340
- assert result.files_analyzed() == 0
341
- assert result.total_issues() == 0
342
- finally:
343
- temp_dir.rmdir()
344
-
345
-
346
- class TestAnalyzerDetectorIntegrationProperties:
347
- """Property-based tests for detector integration."""
348
-
349
- @given(st.booleans())
350
- @settings(max_examples=1000, deadline=None)
351
- def test_disabled_detectors_not_run(self, performance_enabled: bool) -> None:
352
- """Property: Disabled detectors don't produce issues."""
353
- code = """
354
- def test_func():
355
- result = []
356
- for i in range(10):
357
- result.append(i)
358
- return result
359
- """
360
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
361
- f.write(code)
362
- temp_path = Path(f.name)
363
-
364
- try:
365
- config = Config()
366
- config.performance.enabled = performance_enabled
367
-
368
- analyzer = Analyzer(config)
369
- result = analyzer.analyze_file(temp_path)
370
-
371
- # Result should exist regardless
372
- assert result.file_path == str(temp_path)
373
-
374
- # If performance disabled, no performance-specific rules should fire
375
- # Note: This assumes performance rules start with P
376
- # Actual behavior depends on detector implementation
377
- finally:
378
- temp_path.unlink()
379
-
380
- @given(st.integers(min_value=1, max_value=20))
381
- @settings(max_examples=1000, deadline=None)
382
- def test_complexity_threshold_affects_issues(self, max_branches: int) -> None:
383
- """Property: Higher complexity threshold produces fewer issues."""
384
- # Code with 15 branches
385
- code = """
386
- def complex_func(x):
387
- if x == 1: return 1
388
- if x == 2: return 2
389
- if x == 3: return 3
390
- if x == 4: return 4
391
- if x == 5: return 5
392
- if x == 6: return 6
393
- if x == 7: return 7
394
- if x == 8: return 8
395
- if x == 9: return 9
396
- if x == 10: return 10
397
- if x == 11: return 11
398
- if x == 12: return 12
399
- if x == 13: return 13
400
- if x == 14: return 14
401
- if x == 15: return 15
402
- return 0
403
- """
404
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
405
- f.write(code)
406
- temp_path = Path(f.name)
407
-
408
- try:
409
- config = Config(complexity=ComplexityConfig(max_branches=max_branches))
410
- analyzer = Analyzer(config)
411
- result = analyzer.analyze_file(temp_path)
412
-
413
- # If threshold is >= 15, no branch issues
414
- # If threshold is < 15, should have branch issue
415
- branch_issues = [
416
- issue for issue in result.issues if "branches" in issue.message.lower()
417
- ]
418
-
419
- if max_branches >= 15:
420
- # Should have no branch issues
421
- assert len(branch_issues) == 0
422
- else:
423
- # Should have at least one branch issue
424
- assert len(branch_issues) >= 1
425
- finally:
426
- temp_path.unlink()
427
-
428
-
429
- class TestAnalyzerInvariants:
430
- """Test invariants across analyzer operations."""
431
-
432
- def test_analyzing_same_file_twice_produces_same_results(self) -> None:
433
- """Property: Analyzing same file twice produces identical results."""
434
- code = """
435
- def test_func():
436
- if a:
437
- if b:
438
- if c:
439
- pass
440
- """
441
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
442
- f.write(code)
443
- temp_path = Path(f.name)
444
-
445
- try:
446
- config = Config()
447
- analyzer = Analyzer(config)
448
-
449
- result1 = analyzer.analyze_file(temp_path)
450
- result2 = analyzer.analyze_file(temp_path)
451
-
452
- # Results should be identical
453
- assert result1.file_path == result2.file_path
454
- assert result1.lines_of_code == result2.lines_of_code
455
- assert len(result1.issues) == len(result2.issues)
456
- finally:
457
- temp_path.unlink()
458
-
459
- @given(st.lists(exclude_pattern_strategy(), max_size=5))
460
- @settings(max_examples=1000, deadline=None)
461
- def test_analyzer_config_immutable_during_analysis(
462
- self, patterns: list[str]
463
- ) -> None:
464
- """Property: Config doesn't change during analysis."""
465
- config = Config(exclude_patterns=patterns)
466
- original_patterns = config.exclude_patterns.copy()
467
-
468
- analyzer = Analyzer(config)
469
-
470
- # Create and analyze a file
471
- code = "def test(): pass"
472
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
473
- f.write(code)
474
- temp_path = Path(f.name)
475
-
476
- try:
477
- analyzer.analyze_file(temp_path)
478
-
479
- # Config should be unchanged
480
- assert analyzer.config.exclude_patterns == original_patterns
481
- finally:
482
- temp_path.unlink()
483
-
484
- def test_file_analysis_always_has_file_path(self) -> None:
485
- """Property: Every FileAnalysis has a non-empty file path."""
486
- code = "def test(): pass"
487
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
488
- f.write(code)
489
- temp_path = Path(f.name)
490
-
491
- try:
492
- config = Config()
493
- analyzer = Analyzer(config)
494
- result = analyzer.analyze_file(temp_path)
495
-
496
- assert result.file_path != ""
497
- assert len(result.file_path) > 0
498
- finally:
499
- temp_path.unlink()