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
509 lines (442 loc) • 12.1 kB
JavaScript
/**
* astHelpers.js
* AST traversal and manipulation utilities for React/TypeScript parsing
*
* Provides helper functions for working with Babel AST nodes
*/
const t = require('@babel/types');
/**
* Check if node is a React component (class or functional)
* @param {Object} node - AST node
* @returns {boolean}
*/
function isReactComponent(node) {
// Check for class component extending React.Component or Component
if (t.isClassDeclaration(node)) {
if (node.superClass) {
const superClassName = getSuperClassName(node.superClass);
return ['Component', 'React.Component', 'PureComponent', 'React.PureComponent'].includes(superClassName);
}
}
// Check for functional component (arrow function or function declaration)
if (t.isFunctionDeclaration(node) || t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
// Must have a JSX return or JSX in body
return containsJSX(node);
}
return false;
}
/**
* Get the super class name from a class declaration
* @param {Object} superClass - Super class node
* @returns {string}
*/
function getSuperClassName(superClass) {
if (t.isIdentifier(superClass)) {
return superClass.name;
}
if (t.isMemberExpression(superClass)) {
const object = superClass.object.name || '';
const property = superClass.property.name || '';
return `${object}.${property}`;
}
return '';
}
/**
* Check if node contains JSX
* @param {Object} node - AST node
* @returns {boolean}
*/
function containsJSX(node) {
let hasJSX = false;
const checkNode = (n) => {
if (!n) return;
if (t.isJSXElement(n) || t.isJSXFragment(n)) {
hasJSX = true;
return;
}
// Check return statements
if (t.isReturnStatement(n) && n.argument) {
if (t.isJSXElement(n.argument) || t.isJSXFragment(n.argument)) {
hasJSX = true;
return;
}
}
// Recursively check children
if (n.body) {
if (Array.isArray(n.body)) {
n.body.forEach(checkNode);
} else {
checkNode(n.body);
}
}
};
checkNode(node);
return hasJSX;
}
/**
* Extract component name from various node types
* @param {Object} node - AST node
* @returns {string|null}
*/
function getComponentName(node) {
// Class or function declaration
if (t.isClassDeclaration(node) || t.isFunctionDeclaration(node)) {
return node.id ? node.id.name : null;
}
// Variable declaration with component
if (t.isVariableDeclarator(node)) {
return node.id.name;
}
return null;
}
/**
* Extract props parameter from component function
* @param {Object} node - Function node
* @returns {Object|null} - Props parameter node
*/
function getPropsParameter(node) {
if (!node.params || node.params.length === 0) {
return null;
}
const firstParam = node.params[0];
// Handle destructured props: ({ prop1, prop2 }) => {}
if (t.isObjectPattern(firstParam)) {
return firstParam;
}
// Handle typed props: (props: PropsType) => {}
if (t.isIdentifier(firstParam)) {
return firstParam;
}
return null;
}
/**
* Extract destructured prop names from object pattern
* @param {Object} pattern - ObjectPattern node
* @returns {string[]}
*/
function getDestructuredProps(pattern) {
if (!t.isObjectPattern(pattern)) {
return [];
}
return pattern.properties
.filter(prop => t.isObjectProperty(prop) || t.isRestElement(prop))
.map(prop => {
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
return prop.key.name;
}
if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
return `...${prop.argument.name}`;
}
return null;
})
.filter(Boolean);
}
/**
* Check if node is a useState hook call
* @param {Object} node - AST node
* @returns {boolean}
*/
function isUseStateHook(node) {
return t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.callee.name === 'useState';
}
/**
* Check if node is a useEffect hook call
* @param {Object} node - AST node
* @returns {boolean}
*/
function isUseEffectHook(node) {
return t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.callee.name === 'useEffect';
}
/**
* Check if node is a hook call (useXxx)
* @param {Object} node - AST node
* @returns {boolean}
*/
function isHookCall(node) {
return t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.callee.name.startsWith('use') &&
node.callee.name.length > 3 &&
node.callee.name[3] === node.callee.name[3].toUpperCase();
}
/**
* Get hook name from call expression
* @param {Object} node - CallExpression node
* @returns {string|null}
*/
function getHookName(node) {
if (isHookCall(node) && t.isIdentifier(node.callee)) {
return node.callee.name;
}
return null;
}
/**
* Extract state variable name from useState destructuring
* @param {Object} node - VariableDeclarator with useState
* @returns {{name: string, setter: string}|null}
*/
function getStateVariableFromUseState(node) {
if (!t.isVariableDeclarator(node)) {
return null;
}
const init = node.init;
if (!isUseStateHook(init)) {
return null;
}
// Check if destructured: const [value, setValue] = useState()
if (t.isArrayPattern(node.id) && node.id.elements.length >= 2) {
const nameNode = node.id.elements[0];
const setterNode = node.id.elements[1];
if (t.isIdentifier(nameNode) && t.isIdentifier(setterNode)) {
return {
name: nameNode.name,
setter: setterNode.name,
};
}
}
return null;
}
/**
* Check if node is a method call (e.g., fetch, axios.get)
* @param {Object} node - AST node
* @returns {boolean}
*/
function isApiCall(node) {
if (!t.isCallExpression(node)) {
return false;
}
const callee = node.callee;
// Direct function calls: fetch(), axios()
if (t.isIdentifier(callee)) {
const name = callee.name;
return ['fetch', 'axios', 'api', 'request'].includes(name);
}
// Member expressions: axios.get(), api.post()
if (t.isMemberExpression(callee)) {
const object = callee.object;
const property = callee.property;
if (t.isIdentifier(object) && t.isIdentifier(property)) {
const objectName = object.name;
const methodName = property.name;
const apiObjects = ['axios', 'api', 'http', 'client', 'fetch'];
const apiMethods = ['get', 'post', 'put', 'delete', 'patch', 'request'];
return apiObjects.includes(objectName) || apiMethods.includes(methodName);
}
}
return false;
}
/**
* Extract string value from node
* @param {Object} node - AST node
* @returns {string|null}
*/
function getStringValue(node) {
if (t.isStringLiteral(node)) {
return node.value;
}
if (t.isTemplateLiteral(node) && node.quasis.length === 1) {
return node.quasis[0].value.cooked;
}
return null;
}
/**
* Extract number value from node
* @param {Object} node - AST node
* @returns {number|null}
*/
function getNumberValue(node) {
if (t.isNumericLiteral(node)) {
return node.value;
}
return null;
}
/**
* Extract boolean value from node
* @param {Object} node - AST node
* @returns {boolean|null}
*/
function getBooleanValue(node) {
if (t.isBooleanLiteral(node)) {
return node.value;
}
return null;
}
/**
* Get literal value from various node types
* @param {Object} node - AST node
* @returns {*}
*/
function getLiteralValue(node) {
const strVal = getStringValue(node);
if (strVal !== null) return strVal;
const numVal = getNumberValue(node);
if (numVal !== null) return numVal;
const boolVal = getBooleanValue(node);
if (boolVal !== null) return boolVal;
if (t.isNullLiteral(node)) return null;
if (t.isIdentifier(node) && node.name === 'undefined') return undefined;
return null;
}
/**
* Check if node is an import declaration
* @param {Object} node - AST node
* @returns {boolean}
*/
function isImportDeclaration(node) {
return t.isImportDeclaration(node);
}
/**
* Extract import information
* @param {Object} node - ImportDeclaration node
* @returns {Object}
*/
function getImportInfo(node) {
if (!t.isImportDeclaration(node)) {
return null;
}
return {
source: node.source.value,
specifiers: node.specifiers.map(spec => {
if (t.isImportDefaultSpecifier(spec)) {
return { type: 'default', local: spec.local.name };
}
if (t.isImportSpecifier(spec)) {
return {
type: 'named',
local: spec.local.name,
imported: spec.imported.name,
};
}
if (t.isImportNamespaceSpecifier(spec)) {
return { type: 'namespace', local: spec.local.name };
}
return null;
}).filter(Boolean),
};
}
/**
* Check if node is an export declaration
* @param {Object} node - AST node
* @returns {boolean}
*/
function isExportDeclaration(node) {
return t.isExportNamedDeclaration(node) ||
t.isExportDefaultDeclaration(node) ||
t.isExportAllDeclaration(node);
}
/**
* Get export type and name
* @param {Object} node - Export declaration node
* @returns {Object}
*/
function getExportInfo(node) {
if (t.isExportDefaultDeclaration(node)) {
return {
type: 'default',
declaration: node.declaration,
};
}
if (t.isExportNamedDeclaration(node)) {
return {
type: 'named',
specifiers: node.specifiers.map(spec => ({
local: spec.local.name,
exported: spec.exported.name,
})),
declaration: node.declaration,
};
}
return null;
}
/**
* Find all nodes of a specific type in AST
* @param {Object} ast - AST root
* @param {Function} predicate - Function to test each node
* @returns {Array}
*/
function findNodes(ast, predicate) {
const results = [];
const traverse = (node) => {
if (!node || typeof node !== 'object') {
return;
}
if (predicate(node)) {
results.push(node);
}
// Traverse all properties
Object.keys(node).forEach(key => {
const value = node[key];
if (Array.isArray(value)) {
value.forEach(traverse);
} else if (value && typeof value === 'object') {
traverse(value);
}
});
};
traverse(ast);
return results;
}
/**
* Get the line and column from a node's location
* @param {Object} node - AST node with loc property
* @returns {{line: number, column: number}|null}
*/
function getNodeLocation(node) {
if (node && node.loc && node.loc.start) {
return {
line: node.loc.start.line,
column: node.loc.start.column,
};
}
return null;
}
/**
* Extract JSDoc comment from node
* @param {Object} node - AST node
* @returns {string|null}
*/
function getJSDocComment(node) {
if (node && node.leadingComments) {
const jsdocComment = node.leadingComments.find(
comment => comment.type === 'CommentBlock' && comment.value.startsWith('*')
);
return jsdocComment ? jsdocComment.value : null;
}
return null;
}
module.exports = {
// Component detection
isReactComponent,
getSuperClassName,
containsJSX,
getComponentName,
// Props handling
getPropsParameter,
getDestructuredProps,
// Hooks
isUseStateHook,
isUseEffectHook,
isHookCall,
getHookName,
getStateVariableFromUseState,
// API calls
isApiCall,
// Values
getStringValue,
getNumberValue,
getBooleanValue,
getLiteralValue,
// Imports/Exports
isImportDeclaration,
getImportInfo,
isExportDeclaration,
getExportInfo,
// Traversal
findNodes,
getNodeLocation,
getJSDocComment,
};