UNPKG

prprompts-flutter-generator

Version:

AI-powered Flutter development with full automation + official extension support - Generate 32 security-audited guides & auto-implement in 2-3 hours. NEW v5.1: Official Claude Code plugin with hooks, Gemini TOML commands, Qwen MCP settings. Features: Comp

366 lines (321 loc) 11.3 kB
/** * StateManagementDetector.js * Detects state management patterns (Redux, Zustand, Context API, etc.) * * Recommends Flutter equivalent (BLoC, Cubit, Provider, Riverpod) */ const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const t = require('@babel/types'); const { StateManagementPattern } = require('../models/ComponentModel'); const { createModuleLogger } = require('../utils/logger'); const logger = createModuleLogger('StateManagementDetector'); /** * Flutter state management recommendations */ const FlutterStateManagement = { BLOC: 'bloc', CUBIT: 'cubit', PROVIDER: 'provider', RIVERPOD: 'riverpod', GETX: 'getx', }; /** * State Management Detector class */ class StateManagementDetector { constructor() { this.detectedPatterns = []; this.stateStructure = {}; this.actions = []; } /** * Detect state management pattern from source code * @param {string} sourceCode - Source code to analyze * @param {string} filePath - File path for error reporting * @returns {Object} - Detected pattern information */ detect(sourceCode, filePath) { try { logger.info(`Detecting state management in: ${filePath}`); const ast = parser.parse(sourceCode, { sourceType: 'module', plugins: ['jsx', 'typescript'], }); const result = { pattern: null, flutterRecommendation: null, stateStructure: {}, actions: [], complexity: 0, }; let hasRedux = false; let hasZustand = false; let hasContext = false; let hasMobX = false; let hasRecoil = false; traverse(ast, { // Detect imports ImportDeclaration: (path) => { const source = path.node.source.value; if (source.includes('redux') || source === 'react-redux') { hasRedux = true; } else if (source.includes('zustand')) { hasZustand = true; } else if (source.includes('mobx')) { hasMobX = true; } else if (source.includes('recoil')) { hasRecoil = true; } }, // Detect Context API usage CallExpression: (path) => { const node = path.node; // React.createContext or createContext if (t.isIdentifier(node.callee, { name: 'createContext' }) || (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object, { name: 'React' }) && t.isIdentifier(node.callee.property, { name: 'createContext' }))) { hasContext = true; } // Redux hooks: useSelector, useDispatch if (hasRedux) { if (t.isIdentifier(node.callee, { name: 'useSelector' })) { result.actions.push({ type: 'selector', pattern: 'redux' }); } if (t.isIdentifier(node.callee, { name: 'useDispatch' })) { result.actions.push({ type: 'dispatch', pattern: 'redux' }); } } // Zustand: create if (hasZustand && t.isIdentifier(node.callee, { name: 'create' })) { const storeDefinition = this._extractZustandStore(node); if (storeDefinition) { result.stateStructure = storeDefinition; } } // Context API: useContext if (t.isIdentifier(node.callee, { name: 'useContext' })) { result.actions.push({ type: 'context', pattern: 'context' }); } }, // Detect Redux reducers FunctionDeclaration: (path) => { if (hasRedux && path.node.params.length === 2) { const firstParam = path.node.params[0]; const secondParam = path.node.params[1]; // Typical reducer signature: (state, action) => {} if (t.isIdentifier(firstParam) && t.isIdentifier(secondParam) && (firstParam.name === 'state' || secondParam.name === 'action')) { const reducerActions = this._extractReduxActions(path.node); result.actions.push(...reducerActions); } } }, }); // Determine pattern if (hasRedux) { result.pattern = StateManagementPattern.REDUX; result.flutterRecommendation = FlutterStateManagement.BLOC; result.complexity = 8; // Redux is complex } else if (hasZustand) { result.pattern = StateManagementPattern.ZUSTAND; result.flutterRecommendation = FlutterStateManagement.CUBIT; result.complexity = 4; } else if (hasMobX) { result.pattern = StateManagementPattern.MOBX; result.flutterRecommendation = FlutterStateManagement.BLOC; result.complexity = 7; } else if (hasRecoil) { result.pattern = StateManagementPattern.RECOIL; result.flutterRecommendation = FlutterStateManagement.RIVERPOD; result.complexity = 6; } else if (hasContext) { result.pattern = StateManagementPattern.CONTEXT; result.flutterRecommendation = FlutterStateManagement.PROVIDER; result.complexity = 3; } else { result.pattern = StateManagementPattern.USE_STATE; result.flutterRecommendation = FlutterStateManagement.CUBIT; result.complexity = 2; } logger.success(`Detected ${result.pattern}, recommending ${result.flutterRecommendation}`); return result; } catch (error) { logger.error(`Failed to detect state management in ${filePath}: ${error.message}`); return { pattern: StateManagementPattern.USE_STATE, flutterRecommendation: FlutterStateManagement.CUBIT, stateStructure: {}, actions: [], complexity: 2, }; } } /** * Extract Zustand store definition * @private */ _extractZustandStore(callNode) { if (!callNode.arguments || callNode.arguments.length === 0) { return null; } const storeFunc = callNode.arguments[0]; if (!t.isArrowFunctionExpression(storeFunc) && !t.isFunctionExpression(storeFunc)) { return null; } const store = {}; // Extract state and actions from returned object if (t.isObjectExpression(storeFunc.body)) { storeFunc.body.properties.forEach(prop => { if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) { const key = prop.key.name; // State variable if (!t.isArrowFunctionExpression(prop.value) && !t.isFunctionExpression(prop.value)) { store[key] = { type: 'state', initialValue: this._getValueString(prop.value), }; } // Action else { store[key] = { type: 'action', params: prop.value.params.map(p => p.name || 'unknown'), }; } } }); } return store; } /** * Extract Redux actions from reducer * @private */ _extractReduxActions(reducerNode) { const actions = []; traverse(reducerNode, { SwitchCase: (path) => { // Extract action types from switch cases if (path.node.test && t.isMemberExpression(path.node.test)) { const actionType = this._getActionTypeFromCase(path.node.test); if (actionType) { actions.push({ type: 'redux_action', actionType, pattern: 'redux', }); } } }, }, reducerNode.scope); return actions; } /** * Get action type from switch case * @private */ _getActionTypeFromCase(testNode) { // action.type === 'ACTION_TYPE' if (t.isMemberExpression(testNode) && t.isIdentifier(testNode.object, { name: 'action' }) && t.isIdentifier(testNode.property, { name: 'type' })) { return 'action.type'; } return null; } /** * Get value as string * @private */ _getValueString(node) { if (t.isStringLiteral(node)) return `"${node.value}"`; if (t.isNumericLiteral(node)) return String(node.value); if (t.isBooleanLiteral(node)) return String(node.value); if (t.isNullLiteral(node)) return 'null'; if (t.isArrayExpression(node)) return '[]'; if (t.isObjectExpression(node)) return '{}'; return 'unknown'; } /** * Generate Flutter BLoC/Cubit code suggestion * @param {Object} detectionResult - Result from detect() * @returns {Object} - Code generation suggestions */ generateFlutterSuggestion(detectionResult) { const { pattern, flutterRecommendation, stateStructure, actions } = detectionResult; const suggestion = { stateManagement: flutterRecommendation, packages: [], stateClass: '', eventClasses: [], blocClass: '', }; // Add required packages if (flutterRecommendation === FlutterStateManagement.BLOC) { suggestion.packages.push('flutter_bloc: ^8.1.0'); } else if (flutterRecommendation === FlutterStateManagement.CUBIT) { suggestion.packages.push('flutter_bloc: ^8.1.0'); } else if (flutterRecommendation === FlutterStateManagement.PROVIDER) { suggestion.packages.push('provider: ^6.0.0'); } else if (flutterRecommendation === FlutterStateManagement.RIVERPOD) { suggestion.packages.push('flutter_riverpod: ^2.0.0'); } // Generate state class suggestion.stateClass = this._generateStateClass(stateStructure); // Generate events (for BLoC) if (flutterRecommendation === FlutterStateManagement.BLOC) { suggestion.eventClasses = this._generateEventClasses(actions); suggestion.blocClass = this._generateBlocClass(actions); } return suggestion; } /** * Generate Flutter state class * @private */ _generateStateClass(stateStructure) { const properties = Object.entries(stateStructure) .filter(([key, value]) => value.type === 'state') .map(([key, value]) => ` final dynamic ${key};`) .join('\n'); return `class AppState { ${properties} AppState({${Object.keys(stateStructure).map(k => `this.${k}`).join(', ')}}); }`; } /** * Generate Flutter event classes * @private */ _generateEventClasses(actions) { return actions.map(action => { return `class ${this._toPascalCase(action.actionType || action.type)}Event extends AppEvent {}`; }); } /** * Generate Flutter BLoC class * @private */ _generateBlocClass(actions) { return `class AppBloc extends Bloc<AppEvent, AppState> { AppBloc() : super(AppState()) { // Register event handlers } }`; } /** * Convert to PascalCase * @private */ _toPascalCase(str) { return str .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '') .replace(/^(.)/, (_, c) => c.toUpperCase()); } } module.exports = { StateManagementDetector, FlutterStateManagement, };