reveal-cli 0.8.0__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.
@@ -0,0 +1,238 @@
1
+ """
2
+ Tests for JSON/YAML line number support.
3
+
4
+ Ensures that JSON and YAML analyzers return real line numbers
5
+ from the source file, not fake enumerated positions.
6
+ """
7
+
8
+ import unittest
9
+ from reveal.analyzers.json_analyzer import JSONAnalyzer
10
+ from reveal.analyzers.yaml_analyzer import YAMLAnalyzer
11
+
12
+
13
+ class TestJSONLineNumbers(unittest.TestCase):
14
+ """Test JSON analyzer line number extraction."""
15
+
16
+ def test_json_returns_line_numbers(self):
17
+ """JSON analyzer should return line numbers for top-level keys."""
18
+ json_lines = [
19
+ '{',
20
+ ' "name": "test",',
21
+ ' "version": "1.0.0",',
22
+ ' "dependencies": {',
23
+ ' "foo": "1.0"',
24
+ ' }',
25
+ '}'
26
+ ]
27
+
28
+ analyzer = JSONAnalyzer(json_lines)
29
+ structure = analyzer.analyze_structure()
30
+
31
+ # Should have top-level keys
32
+ self.assertIn('top_level_keys', structure)
33
+ keys = structure['top_level_keys']
34
+
35
+ # Should have 3 keys
36
+ self.assertEqual(len(keys), 3)
37
+
38
+ # Each key should be a dict with 'name' and 'line'
39
+ for key in keys:
40
+ self.assertIsInstance(key, dict)
41
+ self.assertIn('name', key)
42
+ self.assertIn('line', key)
43
+
44
+ # Check specific line numbers
45
+ key_dict = {k['name']: k['line'] for k in keys}
46
+
47
+ self.assertEqual(key_dict['name'], 2)
48
+ self.assertEqual(key_dict['version'], 3)
49
+ self.assertEqual(key_dict['dependencies'], 4)
50
+
51
+ def test_json_with_file_path(self):
52
+ """JSON analyzer should use file_path when provided."""
53
+ json_lines = [
54
+ '{',
55
+ ' "key1": "value1",',
56
+ ' "key2": "value2"',
57
+ '}'
58
+ ]
59
+
60
+ analyzer = JSONAnalyzer(json_lines, file_path='/tmp/test.json')
61
+ structure = analyzer.analyze_structure()
62
+
63
+ # Verify file_path is stored
64
+ self.assertEqual(analyzer.file_path, '/tmp/test.json')
65
+
66
+ # Line numbers should still be found
67
+ keys = structure['top_level_keys']
68
+ self.assertEqual(len(keys), 2)
69
+ self.assertEqual(keys[0]['line'], 2)
70
+ self.assertEqual(keys[1]['line'], 3)
71
+
72
+ def test_json_empty_object(self):
73
+ """JSON analyzer should handle empty objects."""
74
+ json_lines = ['{}']
75
+
76
+ analyzer = JSONAnalyzer(json_lines)
77
+ structure = analyzer.analyze_structure()
78
+
79
+ # Should return empty list, not crash
80
+ self.assertEqual(structure['top_level_keys'], [])
81
+
82
+ def test_json_array_top_level(self):
83
+ """JSON analyzer should handle arrays at top level."""
84
+ json_lines = ['[1, 2, 3]']
85
+
86
+ analyzer = JSONAnalyzer(json_lines)
87
+ structure = analyzer.analyze_structure()
88
+
89
+ # Arrays have no keys
90
+ self.assertEqual(structure['top_level_keys'], [])
91
+
92
+ def test_json_parse_error(self):
93
+ """JSON analyzer should handle parse errors gracefully."""
94
+ json_lines = ['{invalid json}']
95
+
96
+ analyzer = JSONAnalyzer(json_lines)
97
+ structure = analyzer.analyze_structure()
98
+
99
+ # Should have error key
100
+ self.assertIn('error', structure)
101
+ self.assertEqual(structure['top_level_keys'], [])
102
+
103
+
104
+ class TestYAMLLineNumbers(unittest.TestCase):
105
+ """Test YAML analyzer line number extraction."""
106
+
107
+ def test_yaml_returns_line_numbers(self):
108
+ """YAML analyzer should return line numbers for top-level keys."""
109
+ yaml_lines = [
110
+ 'name: test-project',
111
+ 'version: 1.0.0',
112
+ 'dependencies:',
113
+ ' foo: 1.0',
114
+ ' bar: 2.0',
115
+ 'scripts:',
116
+ ' test: pytest'
117
+ ]
118
+
119
+ analyzer = YAMLAnalyzer(yaml_lines)
120
+ structure = analyzer.analyze_structure()
121
+
122
+ # Should have top-level keys
123
+ self.assertIn('top_level_keys', structure)
124
+ keys = structure['top_level_keys']
125
+
126
+ # Should have 4 keys
127
+ self.assertEqual(len(keys), 4)
128
+
129
+ # Each key should be a dict with 'name' and 'line'
130
+ for key in keys:
131
+ self.assertIsInstance(key, dict)
132
+ self.assertIn('name', key)
133
+ self.assertIn('line', key)
134
+
135
+ # Check specific line numbers
136
+ key_dict = {k['name']: k['line'] for k in keys}
137
+
138
+ self.assertEqual(key_dict['name'], 1)
139
+ self.assertEqual(key_dict['version'], 2)
140
+ self.assertEqual(key_dict['dependencies'], 3)
141
+ self.assertEqual(key_dict['scripts'], 6)
142
+
143
+ def test_yaml_with_file_path(self):
144
+ """YAML analyzer should use file_path when provided."""
145
+ yaml_lines = [
146
+ 'key1: value1',
147
+ 'key2: value2'
148
+ ]
149
+
150
+ analyzer = YAMLAnalyzer(yaml_lines, file_path='/tmp/test.yaml')
151
+ structure = analyzer.analyze_structure()
152
+
153
+ # Verify file_path is stored
154
+ self.assertEqual(analyzer.file_path, '/tmp/test.yaml')
155
+
156
+ # Line numbers should still be found
157
+ keys = structure['top_level_keys']
158
+ self.assertEqual(len(keys), 2)
159
+ self.assertEqual(keys[0]['line'], 1)
160
+ self.assertEqual(keys[1]['line'], 2)
161
+
162
+ def test_yaml_with_comments(self):
163
+ """YAML analyzer should find keys despite comments."""
164
+ yaml_lines = [
165
+ '# Configuration file',
166
+ 'name: test',
167
+ '# Database settings',
168
+ 'database:',
169
+ ' host: localhost'
170
+ ]
171
+
172
+ analyzer = YAMLAnalyzer(yaml_lines)
173
+ structure = analyzer.analyze_structure()
174
+
175
+ keys = structure['top_level_keys']
176
+ key_dict = {k['name']: k['line'] for k in keys}
177
+
178
+ # Should find correct lines despite comments
179
+ self.assertEqual(key_dict['name'], 2)
180
+ self.assertEqual(key_dict['database'], 4)
181
+
182
+ def test_yaml_empty_document(self):
183
+ """YAML analyzer should handle empty documents."""
184
+ yaml_lines = ['']
185
+
186
+ analyzer = YAMLAnalyzer(yaml_lines)
187
+ structure = analyzer.analyze_structure()
188
+
189
+ # Should return empty list, not crash
190
+ self.assertEqual(structure['top_level_keys'], [])
191
+
192
+ def test_yaml_list_top_level(self):
193
+ """YAML analyzer should handle lists at top level."""
194
+ yaml_lines = ['- item1', '- item2', '- item3']
195
+
196
+ analyzer = YAMLAnalyzer(yaml_lines)
197
+ structure = analyzer.analyze_structure()
198
+
199
+ # Lists have no keys
200
+ self.assertEqual(structure['top_level_keys'], [])
201
+
202
+
203
+ class TestLineNumberComposability(unittest.TestCase):
204
+ """Test that line numbers make output composable with other tools."""
205
+
206
+ def test_json_format_location_helper(self):
207
+ """JSON analyzer should use format_location helper."""
208
+ json_lines = ['{', ' "test": "value"', '}']
209
+
210
+ analyzer = JSONAnalyzer(json_lines, file_path='config.json')
211
+
212
+ # Test format_location helper
213
+ loc = analyzer.format_location(2)
214
+ self.assertEqual(loc, 'config.json:2')
215
+
216
+ # Without file_path, should use L0000 format
217
+ analyzer_no_path = JSONAnalyzer(json_lines)
218
+ loc = analyzer_no_path.format_location(2)
219
+ self.assertEqual(loc, 'L0002')
220
+
221
+ def test_yaml_format_location_helper(self):
222
+ """YAML analyzer should use format_location helper."""
223
+ yaml_lines = ['test: value']
224
+
225
+ analyzer = YAMLAnalyzer(yaml_lines, file_path='config.yaml')
226
+
227
+ # Test format_location helper
228
+ loc = analyzer.format_location(1)
229
+ self.assertEqual(loc, 'config.yaml:1')
230
+
231
+ # Without file_path, should use L0000 format
232
+ analyzer_no_path = YAMLAnalyzer(yaml_lines)
233
+ loc = analyzer_no_path.format_location(1)
234
+ self.assertEqual(loc, 'L0001')
235
+
236
+
237
+ if __name__ == '__main__':
238
+ unittest.main()
@@ -0,0 +1,151 @@
1
+ """Tests for line number functionality."""
2
+
3
+ import unittest
4
+ from pathlib import Path
5
+ from reveal.cli import parse_file_location
6
+ from reveal.analyzers import SQLAnalyzer, PythonAnalyzer, JSONAnalyzer, YAMLAnalyzer
7
+
8
+
9
+ class TestFileLocationParsing(unittest.TestCase):
10
+ """Test file:line parsing."""
11
+
12
+ def test_plain_file(self):
13
+ """Test plain filename without line number."""
14
+ path, start, end = parse_file_location("test.sql")
15
+ self.assertEqual(path, "test.sql")
16
+ self.assertIsNone(start)
17
+ self.assertIsNone(end)
18
+
19
+ def test_file_with_single_line(self):
20
+ """Test file:32 syntax."""
21
+ path, start, end = parse_file_location("test.sql:32")
22
+ self.assertEqual(path, "test.sql")
23
+ self.assertEqual(start, 32)
24
+ self.assertEqual(end, 32)
25
+
26
+ def test_file_with_range(self):
27
+ """Test file:10-50 syntax."""
28
+ path, start, end = parse_file_location("test.sql:10-50")
29
+ self.assertEqual(path, "test.sql")
30
+ self.assertEqual(start, 10)
31
+ self.assertEqual(end, 50)
32
+
33
+ def test_file_with_open_range(self):
34
+ """Test file:10- syntax (from line 10 to end)."""
35
+ path, start, end = parse_file_location("test.sql:10-")
36
+ self.assertEqual(path, "test.sql")
37
+ self.assertEqual(start, 10)
38
+ self.assertIsNone(end)
39
+
40
+
41
+ class TestAnalyzerParameters(unittest.TestCase):
42
+ """Test that all analyzers accept the new parameters."""
43
+
44
+ def test_sql_analyzer_accepts_kwargs(self):
45
+ """SQL analyzer should accept file_path and focus parameters."""
46
+ lines = ["CREATE TABLE test (id INT);"]
47
+ analyzer = SQLAnalyzer(
48
+ lines,
49
+ file_path="test.sql",
50
+ focus_start=1,
51
+ focus_end=10
52
+ )
53
+ self.assertEqual(analyzer.file_path, "test.sql")
54
+ self.assertEqual(analyzer.focus_start, 1)
55
+ self.assertEqual(analyzer.focus_end, 10)
56
+
57
+ def test_python_analyzer_accepts_kwargs(self):
58
+ """Python analyzer should accept file_path and focus parameters."""
59
+ lines = ["def test(): pass"]
60
+ analyzer = PythonAnalyzer(
61
+ lines,
62
+ file_path="test.py",
63
+ focus_start=1,
64
+ focus_end=10
65
+ )
66
+ self.assertEqual(analyzer.file_path, "test.py")
67
+
68
+ def test_json_analyzer_accepts_kwargs(self):
69
+ """JSON analyzer should accept file_path and focus parameters."""
70
+ lines = ['{"key": "value"}']
71
+ analyzer = JSONAnalyzer(
72
+ lines,
73
+ file_path="test.json",
74
+ focus_start=1
75
+ )
76
+ self.assertEqual(analyzer.file_path, "test.json")
77
+
78
+ def test_yaml_analyzer_accepts_kwargs(self):
79
+ """YAML analyzer should accept file_path and focus parameters."""
80
+ lines = ["key: value"]
81
+ analyzer = YAMLAnalyzer(
82
+ lines,
83
+ file_path="test.yaml"
84
+ )
85
+ self.assertEqual(analyzer.file_path, "test.yaml")
86
+
87
+
88
+ class TestPythonAnalyzerLineNumbers(unittest.TestCase):
89
+ """Test Python analyzer returns line numbers."""
90
+
91
+ def test_function_has_line_number(self):
92
+ """Functions should include line numbers."""
93
+ lines = [
94
+ "# comment",
95
+ "def test_func():",
96
+ " pass",
97
+ ]
98
+ analyzer = PythonAnalyzer(lines)
99
+ structure = analyzer.analyze_structure()
100
+
101
+ self.assertEqual(len(structure['functions']), 1)
102
+ func = structure['functions'][0]
103
+ self.assertIsInstance(func, dict)
104
+ self.assertEqual(func['name'], 'test_func')
105
+ self.assertEqual(func['line'], 2)
106
+
107
+ def test_class_has_line_number(self):
108
+ """Classes should include line numbers."""
109
+ lines = [
110
+ "# comment",
111
+ "",
112
+ "class TestClass:",
113
+ " pass",
114
+ ]
115
+ analyzer = PythonAnalyzer(lines)
116
+ structure = analyzer.analyze_structure()
117
+
118
+ self.assertEqual(len(structure['classes']), 1)
119
+ cls = structure['classes'][0]
120
+ self.assertIsInstance(cls, dict)
121
+ self.assertEqual(cls['name'], 'TestClass')
122
+ self.assertEqual(cls['line'], 3)
123
+
124
+
125
+ class TestFormatLocation(unittest.TestCase):
126
+ """Test BaseAnalyzer format_location helper."""
127
+
128
+ def test_format_location_with_file_path(self):
129
+ """Should return filename:line when file_path is set."""
130
+ lines = ["CREATE TABLE test (id INT);"]
131
+ analyzer = SQLAnalyzer(lines, file_path="/path/to/test.sql")
132
+ loc = analyzer.format_location(32)
133
+ self.assertEqual(loc, "/path/to/test.sql:32")
134
+
135
+ def test_format_location_without_file_path(self):
136
+ """Should return L0000 format when no file_path."""
137
+ lines = ["CREATE TABLE test (id INT);"]
138
+ analyzer = SQLAnalyzer(lines)
139
+ loc = analyzer.format_location(32)
140
+ self.assertEqual(loc, "L0032")
141
+
142
+ def test_format_location_none(self):
143
+ """Should return empty string for None."""
144
+ lines = ["CREATE TABLE test (id INT);"]
145
+ analyzer = SQLAnalyzer(lines)
146
+ loc = analyzer.format_location(None)
147
+ self.assertEqual(loc, "")
148
+
149
+
150
+ if __name__ == '__main__':
151
+ unittest.main()
@@ -0,0 +1,220 @@
1
+ """
2
+ Tests for TOML analyzer line number support.
3
+
4
+ Ensures that TOML analyzer returns real line numbers
5
+ from the source file using the find_definition() helper.
6
+ """
7
+
8
+ import unittest
9
+ from reveal.analyzers.toml_analyzer import TOMLAnalyzer
10
+
11
+
12
+ class TestTOMLLineNumbers(unittest.TestCase):
13
+ """Test TOML analyzer line number extraction."""
14
+
15
+ def test_toml_returns_line_numbers_for_keys(self):
16
+ """TOML analyzer should return line numbers for top-level keys."""
17
+ toml_lines = [
18
+ '# Configuration file',
19
+ 'name = "test-project"',
20
+ 'version = "1.0.0"',
21
+ 'debug = true',
22
+ '',
23
+ '[server]',
24
+ 'host = "localhost"',
25
+ 'port = 8080',
26
+ ]
27
+
28
+ analyzer = TOMLAnalyzer(toml_lines)
29
+ structure = analyzer.analyze_structure()
30
+
31
+ # Should have top-level keys
32
+ self.assertIn('top_level_keys', structure)
33
+ keys = structure['top_level_keys']
34
+
35
+ # Should have 3 top-level keys (name, version, debug)
36
+ self.assertEqual(len(keys), 3)
37
+
38
+ # Each key should be a dict with 'name' and 'line'
39
+ for key in keys:
40
+ self.assertIsInstance(key, dict)
41
+ self.assertIn('name', key)
42
+ self.assertIn('line', key)
43
+
44
+ # Check specific line numbers
45
+ key_dict = {k['name']: k['line'] for k in keys}
46
+ self.assertEqual(key_dict['name'], 2)
47
+ self.assertEqual(key_dict['version'], 3)
48
+ self.assertEqual(key_dict['debug'], 4)
49
+
50
+ def test_toml_returns_line_numbers_for_sections(self):
51
+ """TOML analyzer should return line numbers for sections."""
52
+ toml_lines = [
53
+ 'name = "test"',
54
+ '',
55
+ '[server]',
56
+ 'host = "localhost"',
57
+ '',
58
+ '[database]',
59
+ 'driver = "postgresql"',
60
+ '',
61
+ '[logging]',
62
+ 'level = "info"',
63
+ ]
64
+
65
+ analyzer = TOMLAnalyzer(toml_lines)
66
+ structure = analyzer.analyze_structure()
67
+
68
+ # Should have sections
69
+ self.assertIn('sections', structure)
70
+ sections = structure['sections']
71
+
72
+ # Should have 3 sections
73
+ self.assertEqual(len(sections), 3)
74
+
75
+ # Each section should be a dict with 'name' and 'line'
76
+ for section in sections:
77
+ self.assertIsInstance(section, dict)
78
+ self.assertIn('name', section)
79
+ self.assertIn('line', section)
80
+
81
+ # Check specific line numbers
82
+ section_dict = {s['name']: s['line'] for s in sections}
83
+ self.assertEqual(section_dict['server'], 3)
84
+ self.assertEqual(section_dict['database'], 6)
85
+ self.assertEqual(section_dict['logging'], 9)
86
+
87
+ def test_toml_with_file_path(self):
88
+ """TOML analyzer should use file_path when provided."""
89
+ toml_lines = [
90
+ 'key1 = "value1"',
91
+ 'key2 = "value2"'
92
+ ]
93
+
94
+ analyzer = TOMLAnalyzer(toml_lines, file_path='/tmp/test.toml')
95
+ structure = analyzer.analyze_structure()
96
+
97
+ # Verify file_path is stored
98
+ self.assertEqual(analyzer.file_path, '/tmp/test.toml')
99
+
100
+ # Line numbers should still be found
101
+ keys = structure['top_level_keys']
102
+ self.assertEqual(len(keys), 2)
103
+ self.assertEqual(keys[0]['line'], 1)
104
+ self.assertEqual(keys[1]['line'], 2)
105
+
106
+ def test_toml_pyproject_example(self):
107
+ """TOML analyzer should handle real pyproject.toml structure."""
108
+ toml_lines = [
109
+ '[build-system]',
110
+ 'requires = ["setuptools>=61.0"]',
111
+ '',
112
+ '[project]',
113
+ 'name = "reveal"',
114
+ 'version = "0.1.0"',
115
+ 'description = "Progressive file revelation tool"',
116
+ '',
117
+ '[project.scripts]',
118
+ 'reveal = "reveal.cli:main"',
119
+ '',
120
+ '[tool.pytest]',
121
+ 'testpaths = ["tests"]',
122
+ ]
123
+
124
+ analyzer = TOMLAnalyzer(toml_lines)
125
+ structure = analyzer.analyze_structure()
126
+
127
+ sections = structure['sections']
128
+ section_dict = {s['name']: s['line'] for s in sections}
129
+
130
+ # Check section line numbers
131
+ self.assertEqual(section_dict['build-system'], 1)
132
+ self.assertEqual(section_dict['project'], 4)
133
+ self.assertEqual(section_dict['tool'], 12)
134
+
135
+ def test_toml_nested_sections(self):
136
+ """TOML analyzer should count subsections."""
137
+ toml_lines = [
138
+ '[project]',
139
+ 'name = "test"',
140
+ '',
141
+ '[project.dependencies]',
142
+ 'pytest = "^7.0"',
143
+ '',
144
+ '[project.scripts]',
145
+ 'test = "pytest"',
146
+ '',
147
+ '[tool]',
148
+ 'version = "1.0"',
149
+ ]
150
+
151
+ analyzer = TOMLAnalyzer(toml_lines)
152
+ structure = analyzer.analyze_structure()
153
+
154
+ sections = structure['sections']
155
+
156
+ # Find project section
157
+ project_section = next(s for s in sections if s['name'] == 'project')
158
+
159
+ # Should have subsections count
160
+ self.assertIn('subsections', project_section)
161
+ # project.dependencies and project.scripts are subsections
162
+ self.assertGreater(project_section['subsections'], 0)
163
+
164
+ def test_toml_empty_file(self):
165
+ """TOML analyzer should handle empty files."""
166
+ toml_lines = ['']
167
+
168
+ analyzer = TOMLAnalyzer(toml_lines)
169
+ structure = analyzer.analyze_structure()
170
+
171
+ # Should return empty lists, not crash
172
+ self.assertEqual(structure['top_level_keys'], [])
173
+ self.assertEqual(structure['sections'], [])
174
+
175
+ def test_toml_with_comments(self):
176
+ """TOML analyzer should find keys despite comments."""
177
+ toml_lines = [
178
+ '# Application settings',
179
+ 'name = "myapp"',
180
+ '',
181
+ '# Server configuration',
182
+ '[server]',
183
+ 'port = 8080',
184
+ ]
185
+
186
+ analyzer = TOMLAnalyzer(toml_lines)
187
+ structure = analyzer.analyze_structure()
188
+
189
+ keys = structure['top_level_keys']
190
+ sections = structure['sections']
191
+
192
+ # Should find keys on correct lines (not comment lines)
193
+ key_dict = {k['name']: k['line'] for k in keys}
194
+ self.assertEqual(key_dict['name'], 2)
195
+
196
+ section_dict = {s['name']: s['line'] for s in sections}
197
+ self.assertEqual(section_dict['server'], 5)
198
+
199
+
200
+ class TestTOMLComposability(unittest.TestCase):
201
+ """Test that TOML line numbers make output composable."""
202
+
203
+ def test_toml_format_location_helper(self):
204
+ """TOML analyzer should use format_location helper."""
205
+ toml_lines = ['name = "test"']
206
+
207
+ analyzer = TOMLAnalyzer(toml_lines, file_path='config.toml')
208
+
209
+ # Test format_location helper
210
+ loc = analyzer.format_location(1)
211
+ self.assertEqual(loc, 'config.toml:1')
212
+
213
+ # Without file_path, should use L0000 format
214
+ analyzer_no_path = TOMLAnalyzer(toml_lines)
215
+ loc = analyzer_no_path.format_location(1)
216
+ self.assertEqual(loc, 'L0001')
217
+
218
+
219
+ if __name__ == '__main__':
220
+ unittest.main()