autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
229 lines (228 loc) • 9.95 kB
JavaScript
/**
* React Enhancement Pack
* 条件: { languages: ['typescript', 'javascript'], frameworks: ['react', 'nextjs'] }
*
* 覆盖 React 19 生态:
* - Server / Client Components (RSC)
* - Custom Hooks + Hook 组合模式
* - 组件结构约定
* - 状态管理模式 (Context / Zustand / Jotai)
* - Suspense / ErrorBoundary
*/
import { EnhancementPack } from './EnhancementPack.js';
class ReactEnhancement extends EnhancementPack {
get id() {
return 'react';
}
get displayName() {
return 'React 19 Enhancement';
}
get conditions() {
return { languages: ['typescript', 'javascript'], frameworks: ['react', 'nextjs'] };
}
getExtraDimensions() {
return [
{
id: 'hook-pattern-scan',
label: '自定義 Hook 分析',
guide: '自定义 Hook 提取(useXxx 函数 + 内部状态/副作用分析)、Hook 组合模式、Hook 依赖关系、useCallback/useMemo 使用合理性',
tierHint: 2,
knowledgeTypes: ['code-pattern'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-react-hooks',
description: 'Custom React Hook patterns and composition (auto-generated by enhancement)',
},
},
{
id: 'component-structure-scan',
label: '组件结构约定',
guide: '组件目录结构约定(文件组织、barrel export、props/state 命名规范、forwardRef 使用、组件拆分粒度)',
tierHint: 2,
knowledgeTypes: ['code-standard', 'architecture'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-react-components',
description: 'React component structure conventions (auto-generated by enhancement)',
},
},
{
id: 'rsc-boundary-scan',
label: 'Server/Client 边界分析',
guide: 'React Server Components 分析 — "use client" / "use server" 指令分布、Server → Client 数据传递边界、Serializable props 约束、async Server Component 模式',
tierHint: 2,
knowledgeTypes: ['architecture', 'code-pattern'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-react-rsc',
description: 'React Server Component boundaries, directives and data flow (auto-generated by enhancement)',
},
},
{
id: 'state-management-scan',
label: '状态管理分析',
guide: '状态管理模式分析 — Context Provider 层级、Zustand/Jotai/Redux store 结构、useReducer 有限状态机、状态提升 vs 组合',
tierHint: 2,
knowledgeTypes: ['architecture', 'code-pattern'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-react-state',
description: 'React state management topology — Context/Zustand/Redux patterns (auto-generated by enhancement)',
},
},
];
}
getGuardRules() {
return [
{
ruleId: 'react-no-direct-dom',
category: 'correctness',
dimension: 'file',
severity: 'warning',
languages: ['typescript', 'javascript'],
pattern: /document\.(getElementById|querySelector|getElementsBy)/,
message: '避免直接 DOM 操作,使用 React ref 或状态管理',
},
{
ruleId: 'react-no-index-key',
category: 'correctness',
dimension: 'file',
severity: 'warning',
languages: ['typescript', 'javascript'],
pattern: /\.map\s*\([^)]*,\s*(?:index|i|idx)\s*\)\s*=>\s*[\s\S]*?key\s*=\s*\{?\s*(?:index|i|idx)/,
message: '避免使用 index 作为 key,使用稳定的唯一标识符',
},
{
ruleId: 'react-hooks-conditional',
category: 'correctness',
dimension: 'file',
severity: 'error',
languages: ['typescript', 'javascript'],
pattern: /if\s*\([^)]*\)\s*\{[^}]*\buse[A-Z]\w*\s*\(/,
message: 'Hook 不能在条件语句中调用 — 违反 Rules of Hooks(Hook 必须在组件顶层调用)',
},
{
ruleId: 'react-useeffect-missing-cleanup',
category: 'correctness',
dimension: 'file',
severity: 'info',
languages: ['typescript', 'javascript'],
pattern: /useEffect\s*\(\s*\(\)\s*=>\s*\{[^}]*(?:addEventListener|setInterval|setTimeout|subscribe)[^}]*\}\s*,/,
message: 'useEffect 中注册了事件/订阅/定时器但可能缺少清理函数,会导致内存泄漏',
},
{
ruleId: 'react-no-setState-in-render',
category: 'correctness',
dimension: 'file',
severity: 'error',
languages: ['typescript', 'javascript'],
pattern: /(?:^|\n)\s*(?:const|let)\s+\[.*?,\s*set\w+\]\s*=\s*useState[\s\S]*?\n\s*set\w+\(/m,
message: '避免在渲染阶段直接调用 setState — 会导致无限重渲染',
},
{
ruleId: 'react-exhaustive-deps',
category: 'correctness',
dimension: 'file',
severity: 'warning',
languages: ['typescript', 'javascript'],
pattern: /\/\/\s*eslint-disable.*exhaustive-deps/,
message: '不要禁用 exhaustive-deps 规则,修复依赖数组以避免 stale closure',
},
{
ruleId: 'react-use-client-server-mixing',
category: 'correctness',
dimension: 'file',
severity: 'warning',
languages: ['typescript', 'javascript'],
pattern: /['"]use client['"]\s*;?\s*\n[\s\S]*?['"]use server['"]/,
message: '同一文件不能同时包含 "use client" 和 "use server" 指令',
},
];
}
detectPatterns(astSummary) {
const patterns = [];
// ── Custom Hooks ──
for (const m of astSummary.methods || []) {
if (m.name && /^use[A-Z]/.test(m.name) && !m.className) {
patterns.push({ type: 'custom-hook', methodName: m.name, line: m.line, confidence: 0.9 });
}
}
// ── HOC: function withXxx(Component) ──
for (const m of astSummary.methods || []) {
if (m.name && /^with[A-Z]/.test(m.name) && !m.className) {
patterns.push({ type: 'hoc', methodName: m.name, line: m.line, confidence: 0.8 });
}
}
// ── Context Providers ──
for (const cls of astSummary.classes || []) {
const nameLower = cls.name?.toLowerCase() || '';
if (nameLower.includes('provider') || nameLower.includes('context')) {
patterns.push({
type: 'react-context-provider',
className: cls.name,
line: cls.line,
confidence: 0.8,
});
}
}
// ── forwardRef components ──
for (const m of astSummary.methods || []) {
if (m.name && !m.className) {
// 导出的组件函数(大写开头)
if (/^[A-Z]/.test(m.name)) {
patterns.push({
type: 'react-component',
methodName: m.name,
line: m.line,
confidence: 0.7,
});
}
}
}
// ── Error Boundary classes ──
for (const cls of astSummary.classes || []) {
if (cls.superclass && /Component/.test(cls.superclass)) {
const methods = cls.methods || [];
if (methods.includes('componentDidCatch') || methods.includes('getDerivedStateFromError')) {
patterns.push({
type: 'react-error-boundary',
className: cls.name,
line: cls.line,
confidence: 0.95,
});
}
}
}
// ── Zustand/Redux stores ──
for (const m of astSummary.methods || []) {
if (m.name && /^use\w*Store$/.test(m.name) && !m.className) {
patterns.push({
type: 'react-store',
methodName: m.name,
line: m.line,
confidence: 0.85,
});
}
}
// ── React imports ──
const reactImports = (astSummary.imports || []).filter((imp) => imp.includes('react') ||
imp.includes('zustand') ||
imp.includes('jotai') ||
imp.includes('@reduxjs') ||
imp.includes('react-query') ||
imp.includes('@tanstack'));
if (reactImports.length > 0) {
patterns.push({
type: 'react-ecosystem-usage',
importCount: reactImports.length,
confidence: 0.85,
});
}
return patterns;
}
}
export const pack = new ReactEnhancement();