reveal-cli 0.2.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.
- plugins/c-header.yaml +89 -0
- plugins/gdscript.yaml +96 -0
- plugins/python.yaml +94 -0
- plugins/yaml.yaml +87 -0
- reveal/__init__.py +20 -0
- reveal/_archive_old_v0.1/analyzers/__init__.py +39 -0
- reveal/_archive_old_v0.1/analyzers/base.py +143 -0
- reveal/_archive_old_v0.1/analyzers/bash_analyzer.py +202 -0
- reveal/_archive_old_v0.1/analyzers/csharp_analyzer.py +167 -0
- reveal/_archive_old_v0.1/analyzers/gdscript_analyzer.py +201 -0
- reveal/_archive_old_v0.1/analyzers/go_analyzer.py +230 -0
- reveal/_archive_old_v0.1/analyzers/javascript_analyzer.py +272 -0
- reveal/_archive_old_v0.1/analyzers/json_analyzer.py +143 -0
- reveal/_archive_old_v0.1/analyzers/markdown_analyzer.py +116 -0
- reveal/_archive_old_v0.1/analyzers/php_analyzer.py +166 -0
- reveal/_archive_old_v0.1/analyzers/python_analyzer.py +118 -0
- reveal/_archive_old_v0.1/analyzers/rust_analyzer.py +202 -0
- reveal/_archive_old_v0.1/analyzers/sql_analyzer.py +534 -0
- reveal/_archive_old_v0.1/analyzers/text_analyzer.py +35 -0
- reveal/_archive_old_v0.1/analyzers/toml_analyzer.py +137 -0
- reveal/_archive_old_v0.1/analyzers/treesitter_base.py +276 -0
- reveal/_archive_old_v0.1/analyzers/yaml_analyzer.py +127 -0
- reveal/_archive_old_v0.1/breadcrumbs.py +147 -0
- reveal/_archive_old_v0.1/cli.py +445 -0
- reveal/_archive_old_v0.1/core.py +136 -0
- reveal/_archive_old_v0.1/detectors.py +29 -0
- reveal/_archive_old_v0.1/formatters.py +272 -0
- reveal/_archive_old_v0.1/grep_filter.py +85 -0
- reveal/_archive_old_v0.1/plugin_loader.py +178 -0
- reveal/_archive_old_v0.1/registry.py +286 -0
- reveal/analyzers/__init__.py +23 -0
- reveal/analyzers/go.py +13 -0
- reveal/analyzers/jupyter_analyzer.py +226 -0
- reveal/analyzers/markdown.py +79 -0
- reveal/analyzers/python.py +15 -0
- reveal/analyzers/rust.py +13 -0
- reveal/analyzers/yaml_json.py +110 -0
- reveal/base.py +193 -0
- reveal/main.py +216 -0
- reveal/tests/__init__.py +1 -0
- reveal/tests/test_json_yaml_line_numbers.py +238 -0
- reveal/tests/test_line_numbers.py +151 -0
- reveal/tests/test_toml_analyzer.py +220 -0
- reveal/tree_view.py +105 -0
- reveal/treesitter.py +282 -0
- reveal_cli-0.2.0.dist-info/METADATA +319 -0
- reveal_cli-0.2.0.dist-info/RECORD +51 -0
- reveal_cli-0.2.0.dist-info/WHEEL +5 -0
- reveal_cli-0.2.0.dist-info/entry_points.txt +2 -0
- reveal_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
- reveal_cli-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Go file analyzer using tree-sitter.
|
|
3
|
+
|
|
4
|
+
Extracts:
|
|
5
|
+
- Functions
|
|
6
|
+
- Structs
|
|
7
|
+
- Interfaces
|
|
8
|
+
- Methods
|
|
9
|
+
- Imports
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Dict, Any, List
|
|
13
|
+
from .treesitter_base import TreeSitterAnalyzer, TREE_SITTER_AVAILABLE
|
|
14
|
+
from ..registry import register
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register(['.go'], name='Go', icon='🐹')
|
|
18
|
+
class GoAnalyzer(TreeSitterAnalyzer):
|
|
19
|
+
"""
|
|
20
|
+
Analyzer for Go source files.
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
- Functions, methods, structs, interfaces
|
|
24
|
+
- Package and import statements
|
|
25
|
+
- Type definitions
|
|
26
|
+
|
|
27
|
+
All positions are exact (tree-sitter preserves source locations).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
language_name = 'go'
|
|
31
|
+
|
|
32
|
+
def __init__(self, lines: List[str], **kwargs):
|
|
33
|
+
"""Initialize Go analyzer."""
|
|
34
|
+
self.node_type_map = {
|
|
35
|
+
'function': 'function_declaration',
|
|
36
|
+
'import': 'import_declaration',
|
|
37
|
+
}
|
|
38
|
+
super().__init__(lines, **kwargs)
|
|
39
|
+
|
|
40
|
+
def extract_custom(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
41
|
+
"""Extract Go-specific items."""
|
|
42
|
+
return {
|
|
43
|
+
'methods': self._extract_nodes('method_declaration', name_field='name'),
|
|
44
|
+
'interfaces': self._extract_interfaces(),
|
|
45
|
+
'structs': self._extract_structs(),
|
|
46
|
+
'types': self._extract_types(),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def _extract_structs(self) -> List[Dict[str, Any]]:
|
|
50
|
+
"""Extract struct declarations."""
|
|
51
|
+
if not self.tree:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
structs = []
|
|
55
|
+
cursor = self.tree.walk()
|
|
56
|
+
|
|
57
|
+
def visit(cursor):
|
|
58
|
+
node = cursor.node
|
|
59
|
+
|
|
60
|
+
if node.type == 'type_spec':
|
|
61
|
+
# Check if this type_spec contains a struct_type
|
|
62
|
+
for child in node.children:
|
|
63
|
+
if child.type == 'struct_type':
|
|
64
|
+
# Get the name from type_identifier
|
|
65
|
+
name_node = node.child_by_field_name('name')
|
|
66
|
+
if name_node:
|
|
67
|
+
line = node.start_point[0] + 1
|
|
68
|
+
if self.in_focus_range(line):
|
|
69
|
+
structs.append({
|
|
70
|
+
'name': name_node.text.decode('utf-8'),
|
|
71
|
+
'line': line
|
|
72
|
+
})
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
# Recurse
|
|
76
|
+
if cursor.goto_first_child():
|
|
77
|
+
visit(cursor)
|
|
78
|
+
while cursor.goto_next_sibling():
|
|
79
|
+
visit(cursor)
|
|
80
|
+
cursor.goto_parent()
|
|
81
|
+
|
|
82
|
+
visit(cursor)
|
|
83
|
+
return structs
|
|
84
|
+
|
|
85
|
+
def _extract_interfaces(self) -> List[Dict[str, Any]]:
|
|
86
|
+
"""Extract interface declarations."""
|
|
87
|
+
if not self.tree:
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
interfaces = []
|
|
91
|
+
cursor = self.tree.walk()
|
|
92
|
+
|
|
93
|
+
def visit(cursor):
|
|
94
|
+
node = cursor.node
|
|
95
|
+
|
|
96
|
+
if node.type == 'type_spec':
|
|
97
|
+
# Check if this type_spec contains an interface_type
|
|
98
|
+
for child in node.children:
|
|
99
|
+
if child.type == 'interface_type':
|
|
100
|
+
name_node = node.child_by_field_name('name')
|
|
101
|
+
if name_node:
|
|
102
|
+
line = node.start_point[0] + 1
|
|
103
|
+
if self.in_focus_range(line):
|
|
104
|
+
interfaces.append({
|
|
105
|
+
'name': name_node.text.decode('utf-8'),
|
|
106
|
+
'line': line
|
|
107
|
+
})
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
# Recurse
|
|
111
|
+
if cursor.goto_first_child():
|
|
112
|
+
visit(cursor)
|
|
113
|
+
while cursor.goto_next_sibling():
|
|
114
|
+
visit(cursor)
|
|
115
|
+
cursor.goto_parent()
|
|
116
|
+
|
|
117
|
+
visit(cursor)
|
|
118
|
+
return interfaces
|
|
119
|
+
|
|
120
|
+
def _extract_types(self) -> List[Dict[str, Any]]:
|
|
121
|
+
"""Extract type declarations (excluding interfaces and structs)."""
|
|
122
|
+
if not self.tree:
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
types = []
|
|
126
|
+
cursor = self.tree.walk()
|
|
127
|
+
|
|
128
|
+
def visit(cursor):
|
|
129
|
+
node = cursor.node
|
|
130
|
+
|
|
131
|
+
if node.type == 'type_spec':
|
|
132
|
+
# Check that it's not a struct or interface (handled separately)
|
|
133
|
+
has_struct_or_interface = False
|
|
134
|
+
for child in node.children:
|
|
135
|
+
if child.type in ('interface_type', 'struct_type'):
|
|
136
|
+
has_struct_or_interface = True
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
if not has_struct_or_interface:
|
|
140
|
+
name_node = node.child_by_field_name('name')
|
|
141
|
+
if name_node:
|
|
142
|
+
line = node.start_point[0] + 1
|
|
143
|
+
if self.in_focus_range(line):
|
|
144
|
+
types.append({
|
|
145
|
+
'name': name_node.text.decode('utf-8'),
|
|
146
|
+
'line': line
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
# Recurse
|
|
150
|
+
if cursor.goto_first_child():
|
|
151
|
+
visit(cursor)
|
|
152
|
+
while cursor.goto_next_sibling():
|
|
153
|
+
visit(cursor)
|
|
154
|
+
cursor.goto_parent()
|
|
155
|
+
|
|
156
|
+
visit(cursor)
|
|
157
|
+
return types
|
|
158
|
+
|
|
159
|
+
def format_structure(self, structure: Dict[str, Any]) -> List[str]:
|
|
160
|
+
"""
|
|
161
|
+
Custom formatting for Go structure output.
|
|
162
|
+
|
|
163
|
+
Groups items logically:
|
|
164
|
+
1. Package/Imports
|
|
165
|
+
2. Types (structs, interfaces)
|
|
166
|
+
3. Functions
|
|
167
|
+
4. Methods
|
|
168
|
+
"""
|
|
169
|
+
if not TREE_SITTER_AVAILABLE or 'error' in structure:
|
|
170
|
+
return None # Use default formatting
|
|
171
|
+
|
|
172
|
+
lines = []
|
|
173
|
+
|
|
174
|
+
# Imports
|
|
175
|
+
imports = structure.get('imports', [])
|
|
176
|
+
if imports:
|
|
177
|
+
lines.append(f"\nImports ({len(imports)}):")
|
|
178
|
+
for item in imports[:10]:
|
|
179
|
+
loc = self.format_location(item['line'])
|
|
180
|
+
name = item['name']
|
|
181
|
+
if len(name) > 60:
|
|
182
|
+
name = name[:57] + "..."
|
|
183
|
+
lines.append(f" {loc:30} {name}")
|
|
184
|
+
if len(imports) > 10:
|
|
185
|
+
lines.append(f" ... and {len(imports) - 10} more")
|
|
186
|
+
|
|
187
|
+
# Types section
|
|
188
|
+
types = structure.get('types', [])
|
|
189
|
+
structs = structure.get('structs', [])
|
|
190
|
+
interfaces = structure.get('interfaces', [])
|
|
191
|
+
|
|
192
|
+
type_count = len(types) + len(structs) + len(interfaces)
|
|
193
|
+
if type_count > 0:
|
|
194
|
+
lines.append(f"\nTypes ({type_count}):")
|
|
195
|
+
|
|
196
|
+
if structs:
|
|
197
|
+
lines.append(f" Structs ({len(structs)}):")
|
|
198
|
+
for item in structs:
|
|
199
|
+
loc = self.format_location(item['line'])
|
|
200
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
201
|
+
|
|
202
|
+
if interfaces:
|
|
203
|
+
lines.append(f" Interfaces ({len(interfaces)}):")
|
|
204
|
+
for item in interfaces:
|
|
205
|
+
loc = self.format_location(item['line'])
|
|
206
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
207
|
+
|
|
208
|
+
if types:
|
|
209
|
+
lines.append(f" Other Types ({len(types)}):")
|
|
210
|
+
for item in types:
|
|
211
|
+
loc = self.format_location(item['line'])
|
|
212
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
213
|
+
|
|
214
|
+
# Functions
|
|
215
|
+
functions = structure.get('functions', [])
|
|
216
|
+
if functions:
|
|
217
|
+
lines.append(f"\nFunctions ({len(functions)}):")
|
|
218
|
+
for item in functions:
|
|
219
|
+
loc = self.format_location(item['line'])
|
|
220
|
+
lines.append(f" {loc:30} func {item['name']}()")
|
|
221
|
+
|
|
222
|
+
# Methods
|
|
223
|
+
methods = structure.get('methods', [])
|
|
224
|
+
if methods:
|
|
225
|
+
lines.append(f"\nMethods ({len(methods)}):")
|
|
226
|
+
for item in methods:
|
|
227
|
+
loc = self.format_location(item['line'])
|
|
228
|
+
lines.append(f" {loc:30} func {item['name']}()")
|
|
229
|
+
|
|
230
|
+
return lines if lines else None
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JavaScript/JSX file analyzer using tree-sitter.
|
|
3
|
+
|
|
4
|
+
Extracts:
|
|
5
|
+
- Functions (regular, arrow, async)
|
|
6
|
+
- Classes
|
|
7
|
+
- Imports/Exports
|
|
8
|
+
- Variables (const, let, var)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Dict, Any, List
|
|
12
|
+
from .treesitter_base import TreeSitterAnalyzer, TREE_SITTER_AVAILABLE
|
|
13
|
+
from ..registry import register
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@register(['.js', '.jsx', '.mjs', '.cjs'], name='JavaScript', icon='📜')
|
|
17
|
+
class JavaScriptAnalyzer(TreeSitterAnalyzer):
|
|
18
|
+
"""
|
|
19
|
+
Analyzer for JavaScript source files.
|
|
20
|
+
|
|
21
|
+
Features:
|
|
22
|
+
- Functions (declarations, expressions, arrow functions)
|
|
23
|
+
- Classes and methods
|
|
24
|
+
- Import/export statements
|
|
25
|
+
- Variable declarations
|
|
26
|
+
|
|
27
|
+
All positions are exact (tree-sitter preserves source locations).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
language_name = 'javascript'
|
|
31
|
+
|
|
32
|
+
def __init__(self, lines: List[str], **kwargs):
|
|
33
|
+
"""Initialize JavaScript analyzer."""
|
|
34
|
+
self.node_type_map = {
|
|
35
|
+
'function': 'function_declaration',
|
|
36
|
+
'class': 'class_declaration',
|
|
37
|
+
'import': 'import_statement',
|
|
38
|
+
}
|
|
39
|
+
super().__init__(lines, **kwargs)
|
|
40
|
+
|
|
41
|
+
def extract_custom(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
42
|
+
"""Extract JavaScript-specific items."""
|
|
43
|
+
return {
|
|
44
|
+
'arrow_functions': self._extract_arrow_functions(),
|
|
45
|
+
'function_expressions': self._extract_function_expressions(),
|
|
46
|
+
'exports': self._extract_exports(),
|
|
47
|
+
'variables': self._extract_variables(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def _extract_arrow_functions(self) -> List[Dict[str, Any]]:
|
|
51
|
+
"""Extract arrow function expressions."""
|
|
52
|
+
if not self.tree:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
arrow_funcs = []
|
|
56
|
+
cursor = self.tree.walk()
|
|
57
|
+
|
|
58
|
+
def visit(cursor):
|
|
59
|
+
node = cursor.node
|
|
60
|
+
|
|
61
|
+
if node.type == 'arrow_function':
|
|
62
|
+
# Try to get the variable name it's assigned to
|
|
63
|
+
parent = node.parent
|
|
64
|
+
name = '<arrow>'
|
|
65
|
+
|
|
66
|
+
if parent and parent.type == 'variable_declarator':
|
|
67
|
+
name_node = parent.child_by_field_name('name')
|
|
68
|
+
if name_node:
|
|
69
|
+
name = name_node.text.decode('utf-8')
|
|
70
|
+
|
|
71
|
+
line = node.start_point[0] + 1
|
|
72
|
+
if self.in_focus_range(line):
|
|
73
|
+
arrow_funcs.append({
|
|
74
|
+
'name': name,
|
|
75
|
+
'line': line
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# Recurse
|
|
79
|
+
if cursor.goto_first_child():
|
|
80
|
+
visit(cursor)
|
|
81
|
+
while cursor.goto_next_sibling():
|
|
82
|
+
visit(cursor)
|
|
83
|
+
cursor.goto_parent()
|
|
84
|
+
|
|
85
|
+
visit(cursor)
|
|
86
|
+
return arrow_funcs
|
|
87
|
+
|
|
88
|
+
def _extract_function_expressions(self) -> List[Dict[str, Any]]:
|
|
89
|
+
"""Extract function expressions (const fn = function() {})."""
|
|
90
|
+
if not self.tree:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
func_exprs = []
|
|
94
|
+
cursor = self.tree.walk()
|
|
95
|
+
|
|
96
|
+
def visit(cursor):
|
|
97
|
+
node = cursor.node
|
|
98
|
+
|
|
99
|
+
if node.type == 'function' and node.parent:
|
|
100
|
+
parent = node.parent
|
|
101
|
+
if parent.type == 'variable_declarator':
|
|
102
|
+
name_node = parent.child_by_field_name('name')
|
|
103
|
+
if name_node:
|
|
104
|
+
line = node.start_point[0] + 1
|
|
105
|
+
if self.in_focus_range(line):
|
|
106
|
+
func_exprs.append({
|
|
107
|
+
'name': name_node.text.decode('utf-8'),
|
|
108
|
+
'line': line
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
# Recurse
|
|
112
|
+
if cursor.goto_first_child():
|
|
113
|
+
visit(cursor)
|
|
114
|
+
while cursor.goto_next_sibling():
|
|
115
|
+
visit(cursor)
|
|
116
|
+
cursor.goto_parent()
|
|
117
|
+
|
|
118
|
+
visit(cursor)
|
|
119
|
+
return func_exprs
|
|
120
|
+
|
|
121
|
+
def _extract_exports(self) -> List[Dict[str, Any]]:
|
|
122
|
+
"""Extract export statements."""
|
|
123
|
+
if not self.tree:
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
exports = []
|
|
127
|
+
cursor = self.tree.walk()
|
|
128
|
+
|
|
129
|
+
def visit(cursor):
|
|
130
|
+
node = cursor.node
|
|
131
|
+
|
|
132
|
+
if node.type == 'export_statement':
|
|
133
|
+
# Get exported name or use full statement text
|
|
134
|
+
name = node.text.decode('utf-8').strip()
|
|
135
|
+
if len(name) > 60:
|
|
136
|
+
name = name[:57] + "..."
|
|
137
|
+
|
|
138
|
+
line = node.start_point[0] + 1
|
|
139
|
+
if self.in_focus_range(line):
|
|
140
|
+
exports.append({
|
|
141
|
+
'name': name,
|
|
142
|
+
'line': line
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# Recurse
|
|
146
|
+
if cursor.goto_first_child():
|
|
147
|
+
visit(cursor)
|
|
148
|
+
while cursor.goto_next_sibling():
|
|
149
|
+
visit(cursor)
|
|
150
|
+
cursor.goto_parent()
|
|
151
|
+
|
|
152
|
+
visit(cursor)
|
|
153
|
+
return exports
|
|
154
|
+
|
|
155
|
+
def _extract_variables(self) -> List[Dict[str, Any]]:
|
|
156
|
+
"""Extract top-level variable declarations."""
|
|
157
|
+
if not self.tree:
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
variables = []
|
|
161
|
+
cursor = self.tree.walk()
|
|
162
|
+
|
|
163
|
+
def visit(cursor):
|
|
164
|
+
node = cursor.node
|
|
165
|
+
|
|
166
|
+
# Only get top-level variables
|
|
167
|
+
if node.type == 'variable_declaration' and node.parent.type == 'program':
|
|
168
|
+
for child in node.children:
|
|
169
|
+
if child.type == 'variable_declarator':
|
|
170
|
+
name_node = child.child_by_field_name('name')
|
|
171
|
+
if name_node:
|
|
172
|
+
line = child.start_point[0] + 1
|
|
173
|
+
if self.in_focus_range(line):
|
|
174
|
+
variables.append({
|
|
175
|
+
'name': name_node.text.decode('utf-8'),
|
|
176
|
+
'line': line
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
# Recurse
|
|
180
|
+
if cursor.goto_first_child():
|
|
181
|
+
visit(cursor)
|
|
182
|
+
while cursor.goto_next_sibling():
|
|
183
|
+
visit(cursor)
|
|
184
|
+
cursor.goto_parent()
|
|
185
|
+
|
|
186
|
+
visit(cursor)
|
|
187
|
+
return variables
|
|
188
|
+
|
|
189
|
+
def format_structure(self, structure: Dict[str, Any]) -> List[str]:
|
|
190
|
+
"""
|
|
191
|
+
Custom formatting for JavaScript structure output.
|
|
192
|
+
|
|
193
|
+
Groups items logically:
|
|
194
|
+
1. Imports/Exports
|
|
195
|
+
2. Classes
|
|
196
|
+
3. Functions (declarations, expressions, arrow)
|
|
197
|
+
4. Variables
|
|
198
|
+
"""
|
|
199
|
+
if not TREE_SITTER_AVAILABLE or 'error' in structure:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
lines = []
|
|
203
|
+
|
|
204
|
+
# Imports
|
|
205
|
+
imports = structure.get('imports', [])
|
|
206
|
+
if imports:
|
|
207
|
+
lines.append(f"\nImports ({len(imports)}):")
|
|
208
|
+
for item in imports[:10]:
|
|
209
|
+
loc = self.format_location(item['line'])
|
|
210
|
+
name = item['name']
|
|
211
|
+
if len(name) > 60:
|
|
212
|
+
name = name[:57] + "..."
|
|
213
|
+
lines.append(f" {loc:30} {name}")
|
|
214
|
+
if len(imports) > 10:
|
|
215
|
+
lines.append(f" ... and {len(imports) - 10} more")
|
|
216
|
+
|
|
217
|
+
# Exports
|
|
218
|
+
exports = structure.get('exports', [])
|
|
219
|
+
if exports:
|
|
220
|
+
lines.append(f"\nExports ({len(exports)}):")
|
|
221
|
+
for item in exports[:10]:
|
|
222
|
+
loc = self.format_location(item['line'])
|
|
223
|
+
lines.append(f" {loc:30} {item['name']}")
|
|
224
|
+
if len(exports) > 10:
|
|
225
|
+
lines.append(f" ... and {len(exports) - 10} more")
|
|
226
|
+
|
|
227
|
+
# Classes
|
|
228
|
+
classes = structure.get('classes', [])
|
|
229
|
+
if classes:
|
|
230
|
+
lines.append(f"\nClasses ({len(classes)}):")
|
|
231
|
+
for item in classes:
|
|
232
|
+
loc = self.format_location(item['line'])
|
|
233
|
+
lines.append(f" {loc:30} class {item['name']}")
|
|
234
|
+
|
|
235
|
+
# Functions
|
|
236
|
+
functions = structure.get('functions', [])
|
|
237
|
+
arrow_functions = structure.get('arrow_functions', [])
|
|
238
|
+
func_expressions = structure.get('function_expressions', [])
|
|
239
|
+
|
|
240
|
+
func_count = len(functions) + len(arrow_functions) + len(func_expressions)
|
|
241
|
+
if func_count > 0:
|
|
242
|
+
lines.append(f"\nFunctions ({func_count}):")
|
|
243
|
+
|
|
244
|
+
if functions:
|
|
245
|
+
lines.append(f" Declarations ({len(functions)}):")
|
|
246
|
+
for item in functions:
|
|
247
|
+
loc = self.format_location(item['line'])
|
|
248
|
+
lines.append(f" {loc:28} function {item['name']}()")
|
|
249
|
+
|
|
250
|
+
if arrow_functions:
|
|
251
|
+
lines.append(f" Arrow Functions ({len(arrow_functions)}):")
|
|
252
|
+
for item in arrow_functions:
|
|
253
|
+
loc = self.format_location(item['line'])
|
|
254
|
+
lines.append(f" {loc:28} {item['name']} =>")
|
|
255
|
+
|
|
256
|
+
if func_expressions:
|
|
257
|
+
lines.append(f" Expressions ({len(func_expressions)}):")
|
|
258
|
+
for item in func_expressions:
|
|
259
|
+
loc = self.format_location(item['line'])
|
|
260
|
+
lines.append(f" {loc:28} {item['name']}")
|
|
261
|
+
|
|
262
|
+
# Variables
|
|
263
|
+
variables = structure.get('variables', [])
|
|
264
|
+
if variables:
|
|
265
|
+
lines.append(f"\nVariables ({len(variables)}):")
|
|
266
|
+
for item in variables[:15]:
|
|
267
|
+
loc = self.format_location(item['line'])
|
|
268
|
+
lines.append(f" {loc:30} {item['name']}")
|
|
269
|
+
if len(variables) > 15:
|
|
270
|
+
lines.append(f" ... and {len(variables) - 15} more")
|
|
271
|
+
|
|
272
|
+
return lines if lines else None
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""JSON file analyzer."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, Any, List
|
|
5
|
+
from .base import BaseAnalyzer
|
|
6
|
+
from ..registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register(['.json', '.jsonc', '.json5'], name='JSON', icon='📊')
|
|
10
|
+
class JSONAnalyzer(BaseAnalyzer):
|
|
11
|
+
"""Analyzer for JSON data files"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, lines: List[str], **kwargs):
|
|
14
|
+
super().__init__(lines, **kwargs)
|
|
15
|
+
self.parse_error = None
|
|
16
|
+
self.parsed_data = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
content = '\n'.join(lines)
|
|
20
|
+
self.parsed_data = json.loads(content)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
self.parse_error = str(e)
|
|
23
|
+
|
|
24
|
+
def analyze_structure(self) -> Dict[str, Any]:
|
|
25
|
+
"""Analyze JSON structure."""
|
|
26
|
+
if self.parse_error:
|
|
27
|
+
return {
|
|
28
|
+
'error': self.parse_error,
|
|
29
|
+
'top_level_keys': [],
|
|
30
|
+
'object_count': 0,
|
|
31
|
+
'array_count': 0,
|
|
32
|
+
'max_depth': 0,
|
|
33
|
+
'value_types': {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Get top-level keys with line numbers
|
|
37
|
+
top_level_keys = []
|
|
38
|
+
if isinstance(self.parsed_data, dict):
|
|
39
|
+
for key in self.parsed_data.keys():
|
|
40
|
+
# Find where this key is defined in the source file
|
|
41
|
+
# Look for the key as a quoted string (JSON format)
|
|
42
|
+
line_num = self.find_definition(f'"{key}"', case_sensitive=True)
|
|
43
|
+
if line_num is None:
|
|
44
|
+
# Fallback: try without quotes (for malformed JSON)
|
|
45
|
+
line_num = self.find_definition(key, case_sensitive=True)
|
|
46
|
+
|
|
47
|
+
top_level_keys.append({
|
|
48
|
+
'name': key,
|
|
49
|
+
'line': line_num if line_num is not None else 1
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
# Count objects and arrays
|
|
53
|
+
object_count, array_count = self._count_structures(self.parsed_data)
|
|
54
|
+
|
|
55
|
+
# Calculate max depth
|
|
56
|
+
max_depth = self._calculate_depth(self.parsed_data)
|
|
57
|
+
|
|
58
|
+
# Count value types
|
|
59
|
+
value_types = self._count_value_types(self.parsed_data)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
'top_level_keys': top_level_keys,
|
|
63
|
+
'object_count': object_count,
|
|
64
|
+
'array_count': array_count,
|
|
65
|
+
'max_depth': max_depth,
|
|
66
|
+
'value_types': value_types
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def _count_structures(self, obj: Any) -> tuple[int, int]:
|
|
70
|
+
"""Count objects and arrays recursively."""
|
|
71
|
+
object_count = 0
|
|
72
|
+
array_count = 0
|
|
73
|
+
|
|
74
|
+
if isinstance(obj, dict):
|
|
75
|
+
object_count += 1
|
|
76
|
+
for value in obj.values():
|
|
77
|
+
obj_c, arr_c = self._count_structures(value)
|
|
78
|
+
object_count += obj_c
|
|
79
|
+
array_count += arr_c
|
|
80
|
+
elif isinstance(obj, list):
|
|
81
|
+
array_count += 1
|
|
82
|
+
for item in obj:
|
|
83
|
+
obj_c, arr_c = self._count_structures(item)
|
|
84
|
+
object_count += obj_c
|
|
85
|
+
array_count += arr_c
|
|
86
|
+
|
|
87
|
+
return object_count, array_count
|
|
88
|
+
|
|
89
|
+
def _calculate_depth(self, obj: Any, current_depth: int = 0) -> int:
|
|
90
|
+
"""Calculate maximum nesting depth."""
|
|
91
|
+
if isinstance(obj, dict):
|
|
92
|
+
if not obj:
|
|
93
|
+
return current_depth
|
|
94
|
+
return max(self._calculate_depth(v, current_depth + 1) for v in obj.values())
|
|
95
|
+
elif isinstance(obj, list):
|
|
96
|
+
if not obj:
|
|
97
|
+
return current_depth
|
|
98
|
+
return max(self._calculate_depth(item, current_depth + 1) for item in obj)
|
|
99
|
+
else:
|
|
100
|
+
return current_depth
|
|
101
|
+
|
|
102
|
+
def _count_value_types(self, obj: Any) -> Dict[str, int]:
|
|
103
|
+
"""Count value types in JSON."""
|
|
104
|
+
types = {}
|
|
105
|
+
|
|
106
|
+
def count_type(value):
|
|
107
|
+
type_name = type(value).__name__
|
|
108
|
+
if type_name == 'NoneType':
|
|
109
|
+
type_name = 'null'
|
|
110
|
+
elif type_name == 'bool':
|
|
111
|
+
type_name = 'boolean'
|
|
112
|
+
elif type_name == 'int' or type_name == 'float':
|
|
113
|
+
type_name = 'number'
|
|
114
|
+
types[type_name] = types.get(type_name, 0) + 1
|
|
115
|
+
|
|
116
|
+
def traverse(item):
|
|
117
|
+
if isinstance(item, dict):
|
|
118
|
+
for value in item.values():
|
|
119
|
+
traverse(value)
|
|
120
|
+
elif isinstance(item, list):
|
|
121
|
+
for element in item:
|
|
122
|
+
traverse(element)
|
|
123
|
+
else:
|
|
124
|
+
count_type(item)
|
|
125
|
+
|
|
126
|
+
traverse(obj)
|
|
127
|
+
return types
|
|
128
|
+
|
|
129
|
+
def generate_preview(self) -> List[tuple[int, str]]:
|
|
130
|
+
"""Generate JSON preview (first 10 key/value pairs or 20 lines)."""
|
|
131
|
+
preview = []
|
|
132
|
+
|
|
133
|
+
if self.parse_error:
|
|
134
|
+
# Fallback to first 20 lines
|
|
135
|
+
for i, line in enumerate(self.lines[:20], 1):
|
|
136
|
+
preview.append((i, line))
|
|
137
|
+
return preview
|
|
138
|
+
|
|
139
|
+
# Show first 20 lines
|
|
140
|
+
for i, line in enumerate(self.lines[:20], 1):
|
|
141
|
+
preview.append((i, line))
|
|
142
|
+
|
|
143
|
+
return preview
|