Skip to content

Commit 241d2bd

Browse files
author
ChidcGithub
committed
v0.7.1: Social Card Generator & UI Improvements
New Features: - Social Card Generator with Brand/2D/3D preview options - Toast system improvements (top position, 15 max, 24px radius) Known Issues: - Social Card export may be unstable in certain conditions
1 parent a1ea8b5 commit 241d2bd

33 files changed

Lines changed: 2345 additions & 485 deletions

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# PyVizAST
1515

16-
[![Version](https://img.shields.io/badge/Version-0.7.0-blue.svg)](https://github.com/ChidcGithub/PyVizAST)
16+
[![Version](https://img.shields.io/badge/Version-0.7.1-blue.svg)](https://github.com/ChidcGithub/PyVizAST)
1717
[![Python](https://img.shields.io/badge/Python-3.8%2B-brightgreen.svg)](https://www.python.org/)
1818
[![License](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](LICENSE)
1919
[![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)](https://github.com/ChidcGithub/PyVizAST)
@@ -207,12 +207,57 @@ GNU General Public License v3.0
207207

208208
Contributions are welcome. Please submit pull requests to the main repository.
209209

210+
## Known Issues
211+
212+
- **Social Card Export**: The social card generator may be unstable in certain browsers. 3D visualization capture requires the 3D view to be fully rendered before capturing.
213+
- **Gesture Control**: Gesture recognition may be unstable in certain lighting conditions.
214+
210215
---
211216

212217
<details>
213218

214219
<summary>Version History</summary>
215220

221+
<details>
222+
<summary>v0.7.1 (2026-03-14)</summary>
223+
224+
**Social Card Generator & UI Improvements**
225+
226+
**New Features:**
227+
- **Social Card Generator**: Generate shareable image cards from the Share dropdown
228+
- Brand card with AST visualization, logo, and feature highlights
229+
- 2D Preview card with captured AST visualization
230+
- 3D Preview card with captured 3D AST visualization
231+
- Black/white theme adaptation
232+
- PNG download (1200x630px standard social share size)
233+
234+
**Toast System Improvements:**
235+
- Toast position moved to top-right of screen
236+
- Simplified vertical stacking (removed complex stacking logic)
237+
- Increased maximum toast limit from 5 to 15
238+
- Increased border-radius from 16px to 24px for rounder corners
239+
240+
**UI Enhancements:**
241+
- Share button now shows dropdown menu with "Share Code" and "Social Card" options
242+
- Visualizer components expose `captureScreenshot` method for card generation
243+
- Improved card preview modal with style selector
244+
245+
**Known Issues:**
246+
- Social Card export may be unstable in certain browsers/conditions
247+
- 3D visualization capture requires the 3D view to be fully rendered
248+
249+
**Files Modified:**
250+
- `frontend/src/components/SocialCardGenerator.js` - New social card generator component
251+
- `frontend/src/components/Header.js` - Share dropdown menu
252+
- `frontend/src/components/Toast.js` - Simplified toast system
253+
- `frontend/src/components/ToastContext.js` - Increased toast limit
254+
- `frontend/src/App.js` - Screenshot capture handlers
255+
- `frontend/src/App.css` - Social card styles, toast improvements
256+
- `frontend/src/components/ASTVisualizer.js` - Screenshot capture method
257+
- `frontend/src/components/ASTVisualizer3D.js` - Screenshot capture method
258+
259+
</details>
260+
216261
<details>
217262
<summary>v0.7.0 (2026-03-14)</summary>
218263

backend/analyzers/code_smells.py

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -245,29 +245,71 @@ def visit_Constant(self, node):
245245
visitor.visit(tree)
246246

247247
def _detect_unused_variables(self, tree: ast.AST):
248-
"""Detect unused variables"""
249-
assigned_vars = set()
250-
used_vars = set()
248+
"""Detect unused variables - Enhanced version with unpacking support"""
249+
assigned_vars = set() # (var_name, lineno)
250+
used_vars = set() # var_name
251+
252+
# Helper function to extract variable names from assignment targets
253+
def extract_var_names(target, lineno):
254+
"""Extract variable names from assignment target, handling unpacking"""
255+
names = []
256+
if isinstance(target, ast.Name):
257+
names.append((target.id, lineno))
258+
elif isinstance(target, ast.Tuple) or isinstance(target, ast.List):
259+
# Handle tuple/list unpacking: a, b = func() or [a, b] = func()
260+
for elt in target.elts:
261+
names.extend(extract_var_names(elt, lineno))
262+
elif isinstance(target, ast.Starred):
263+
# Handle starred unpacking: a, *rest = func()
264+
names.extend(extract_var_names(target.value, lineno))
265+
return names
251266

252267
for node in ast.walk(tree):
268+
# Handle regular assignments
269+
if isinstance(node, ast.Assign):
270+
for target in node.targets:
271+
assigned_vars.update(extract_var_names(target, node.lineno))
272+
273+
# Handle annotated assignments: x: int = 5
274+
elif isinstance(node, ast.AnnAssign):
275+
if isinstance(node.target, ast.Name):
276+
assigned_vars.add((node.target.id, node.lineno))
277+
278+
# Handle augmented assignments: x += 1 (counts as both assign and use)
279+
elif isinstance(node, ast.AugAssign):
280+
if isinstance(node.target, ast.Name):
281+
used_vars.add(node.target.id)
282+
283+
# Handle for loop variables: for i in range(10):
284+
elif isinstance(node, ast.For):
285+
assigned_vars.update(extract_var_names(node.target, node.lineno))
286+
287+
# Handle with statement variables: with open() as f:
288+
elif isinstance(node, ast.withitem):
289+
if node.optional_vars:
290+
assigned_vars.update(extract_var_names(node.optional_vars, node.lineno if hasattr(node, 'lineno') else 0))
291+
292+
# Handle variable usage
253293
if isinstance(node, ast.Name):
254-
if isinstance(node.ctx, ast.Store):
255-
assigned_vars.add((node.id, node.lineno))
256-
elif isinstance(node.ctx, ast.Load):
294+
if isinstance(node.ctx, ast.Load):
257295
used_vars.add(node.id)
258296

259297
# Find defined but unused variables
260298
for var_name, lineno in assigned_vars:
261-
if var_name not in used_vars and not var_name.startswith('_'):
262-
# Exclude some special cases
263-
if var_name not in ('_', 'self', 'cls'):
264-
self.issues.append(CodeIssue(
265-
id=self._generate_issue_id("unused_var"),
266-
type="code_smell",
267-
severity=SeverityLevel.INFO,
268-
message=f"Variable '{var_name}' may be unused",
269-
lineno=lineno
270-
))
299+
# Skip special cases
300+
if var_name.startswith('_'): # Convention for intentionally unused
301+
continue
302+
if var_name in ('self', 'cls', 'args', 'kwargs'): # Special parameters
303+
continue
304+
305+
if var_name not in used_vars:
306+
self.issues.append(CodeIssue(
307+
id=self._generate_issue_id("unused_var"),
308+
type="code_smell",
309+
severity=SeverityLevel.INFO,
310+
message=f"Variable '{var_name}' may be unused",
311+
lineno=lineno
312+
))
271313

272314
def _detect_poor_names(self, tree: ast.AST):
273315
"""Detect poor naming"""

backend/analyzers/complexity.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,13 @@ def visit_Call(self, node):
192192
if self.current_function:
193193
func_name = None
194194
is_self_call = False
195+
is_class_call = False # ClassName.method(self) pattern
195196

196197
if isinstance(node.func, ast.Name):
197198
# Direct function call: func()
198199
func_name = node.func.id
199200
elif isinstance(node.func, ast.Attribute):
200-
# Method call: self.func() or obj.func()
201+
# Method call: self.func(), obj.func(), or ClassName.method()
201202
if isinstance(node.func.value, ast.Name):
202203
if node.func.value.id == 'self':
203204
# self.method() - potential method recursion
@@ -207,12 +208,24 @@ def visit_Call(self, node):
207208
# cls.method() - class method recursion
208209
is_self_call = True
209210
func_name = node.func.attr
211+
elif node.func.value.id == self.current_class:
212+
# ClassName.method(self) - static method call within the same class
213+
# This is also a form of recursion if method name matches
214+
is_class_call = True
215+
func_name = node.func.attr
210216

211217
# Add complexity for recursive calls
212-
# For self.method(), only count if we're inside a class and method name matches
213218
if func_name == self.current_function:
214219
if is_self_call:
215220
# self.current_method() inside the same method = definite recursion
221+
# Note: current_class must be set for self.method() to be valid
222+
# In module-level functions, 'self' doesn't exist, so this check
223+
# correctly ignores invalid AST patterns
224+
if self.current_class is not None:
225+
self.complexity += 1
226+
elif is_class_call:
227+
# ClassName.method(self) call within the same class
228+
# This is recursion if we're inside that method
216229
if self.current_class is not None:
217230
self.complexity += 1
218231
else:

backend/analyzers/performance.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,30 @@ def visit_Call(self, node):
337337
def _detect_expensive_operations_in_loops(self, tree: ast.AST):
338338
"""Detect expensive operations inside loops"""
339339

340+
# Expensive operations that should be avoided in loops
340341
expensive_calls = {
341-
'open', 'read', 'write', 'connect', 'request',
342-
'query', 'execute', 'fetch', 'commit'
342+
# I/O operations
343+
'open', 'read', 'write', 'close', 'flush',
344+
# Network operations
345+
'connect', 'request', 'send', 'recv', 'urlopen',
346+
# Database operations
347+
'query', 'execute', 'fetch', 'commit', 'rollback', 'cursor',
348+
# File system operations
349+
'listdir', 'walk', 'glob', 'stat', 'exists', 'isfile', 'isdir', 'mkdir', 'rmdir', 'remove',
350+
# Serialization
351+
'dumps', 'loads', 'dump', 'load',
352+
# Heavy computations
353+
'sort', 'sorted', 'reverse', 'reversed',
354+
# Regex compilation
355+
'compile', 'match', 'search', 'findall', 'sub',
356+
# Sleep (blocks execution)
357+
'sleep',
358+
# Image processing
359+
'imread', 'imwrite', 'resize', 'save', 'show',
360+
# Cryptographic operations
361+
'encrypt', 'decrypt', 'hash', 'sign', 'verify',
362+
# HTTP requests
363+
'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
343364
}
344365

345366
class ExpensiveOpVisitor(ast.NodeVisitor):

backend/ast_parser/parser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def parse(self, code: str, source_lines: Optional[List[str]] = None, tree: Optio
8080
raise ValueError("Code too large to parse (out of memory). Try simplifying the code or use simplified mode.")
8181
except RecursionError:
8282
raise ValueError("Code structure too deeply nested to parse.")
83+
except (ValueError, TypeError) as e:
84+
# Handle encoding issues, invalid characters, or type errors
85+
raise ValueError(f"Invalid code format: {e}")
86+
except Exception as e:
87+
# Catch-all for any other unexpected errors during parsing
88+
raise ValueError(f"Failed to parse code: {type(e).__name__}: {e}")
8389

8490
if source_lines is None:
8591
source_lines = code.splitlines()

backend/data/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""
2-
PyVizAST 数据模块
3-
包含挑战数据等静态资源
2+
PyVizAST Data Module
3+
Contains static resources such as challenge data
44
"""

backend/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
5858
app = FastAPI(
5959
title="PyVizAST API",
6060
description="Python AST Visualization and Static Analysis API",
61-
version="0.7.0",
61+
version="0.7.1",
6262
docs_url="/docs",
6363
redoc_url="/redoc",
6464
lifespan=lifespan

0 commit comments

Comments
 (0)