diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index c092546..6aa7efe 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -140,7 +140,7 @@ | copy 复制 | `copy` | setting 设置 | `sliders-horizontal` | label 标签 | `tag` | | health 健康检查 | `activity` | start 启动 | `play` | | | - 库规则的种类用规则给的图标(`KindBadge.icon`):SQLAlchemy 数据表 `database`、数据类 `package`、Celery 应用 `server-cog`、定时任务 `calendar-clock`、FastAPI 接口 `globe`、FastAPI 路由器 `network`。 + 库规则的种类用规则给的图标(`KindBadge.icon`):SQLAlchemy 数据表 `database`、数据类 `package`、Celery 应用 `server-cog`、定时任务 `calendar-clock`、FastAPI 接口 `globe`、FastAPI 路由器 `network`、React 组件 `atom`、副作用 `zap`、事件处理 `mouse-pointer-click`。 这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置共用一个,字段、标签共用一个——加库规则用的 9 个(React 组件的原子、副作用的闪电、事件处理的点击手势也在里面),lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,`KindBadge.icon` 只能写其中一个,写别的是类型错误。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。 - 右上角的图标按钮(原型组件 `Reader / BlockTools`,普通块、属性表块、库规则的卡片共用):只有图标、不带字,名字写在悬停提示和 `aria-label` 里;整排是 `role="group"`(`aria-label="块操作"`)。先是「问 AI」(sparkles 图标,点开 / 收起块下面的问答区,展开时带按下的底色、`aria-expanded`;文件不在工作区文件夹里时没有,见「问 AI」),再是「编辑 / 预览」切换:预览时铅笔「编辑」,编辑时眼睛「预览」、带按下的底色(`--vscode-toolbar-activeBackground`)。按钮宽 24px、高 20px(和种类图标同高),图标 14px、`descriptionForeground` 色;悬停 `--vscode-toolbar-hoverBackground`,键盘聚焦描 1px `--vscode-focusBorder`;Tab 能走到,回车 / 空格切换。 diff --git a/vscode/src/codeReader/rules/react.ts b/vscode/src/codeReader/rules/react.ts index 1a03184..9303606 100644 --- a/vscode/src/codeReader/rules/react.ts +++ b/vscode/src/codeReader/rules/react.ts @@ -391,6 +391,18 @@ interface Analysis { body: BodyFacts } +/** 一棵语法树里各组件体读出来的(按组件主体的起始下标):认领组件和它里面每一块时都要用,同一棵树读一次就够。 */ +const bodies = new WeakMap>() + +function analysisOf(component: ComponentFacts): Analysis { + const tree = component.body.tree + const known = bodies.get(tree) ?? new Map() + bodies.set(tree, known) + const body = known.get(component.body.startIndex) ?? bodyOf(component) + known.set(component.body.startIndex, body) + return { component, body } +} + function keyword(text: string): Inline { return { text, role: 'keyword' } } @@ -724,7 +736,7 @@ function nestedCard(block: CodeBlock, env: Env): RuleCard | undefined { const component = enclosingComponent(block.node) if (!component) return undefined - const body = bodyOf(component) + const { body } = analysisOf(component) const at = (facts: { statement: Node }): boolean => sameNode(facts.statement, block.node) const effect = body.effects.find(at) if (effect) @@ -927,7 +939,7 @@ function renderRow(analysis: Analysis, env: Env): PropertyRow { /** 组件卡片。 */ function componentCard(block: CodeBlock, component: ComponentFacts, env: Env): RuleCard { const { words } = env - const analysis: Analysis = { component, body: bodyOf(component) } + const analysis = analysisOf(component) const { body } = analysis const rows: PropertyRow[] = [nameRow(component, env), ...docRows(component, env), propsRow(component, env)] const absorbs: CodeBlock[] = [] diff --git a/vscode/src/codeReader/rules/reactTree.ts b/vscode/src/codeReader/rules/reactTree.ts index 6741735..0ba5f29 100644 --- a/vscode/src/codeReader/rules/reactTree.ts +++ b/vscode/src/codeReader/rules/reactTree.ts @@ -282,13 +282,26 @@ export function componentsIn(root: Node): ComponentFacts[] { /** 语句直接写在哪个组件的组件体里(组件体是语句块时);不是组件体里的语句时没有。 */ export function enclosingComponent(statement: Node): ComponentFacts | undefined { const body = statement.parent - if (body?.type !== 'statement_block') + const fn = body?.type === 'statement_block' ? body.parent : null + if (!fn || !isFunction(fn)) return undefined - let outer: Node | null = body.parent - while (outer && !['export_statement', 'lexical_declaration', 'variable_declaration', 'function_declaration'].includes(outer.type)) + // 组件函数只会是函数声明、变量的值,或 memo / forwardRef 的实参(可以套几层);别的函数(副作用、处理函数)里的语句不用再往上找 + let outer: Node | null = fn + if (fn.type !== 'function_declaration') { + let value: Node = fn + for (let parent = value.parent; parent; parent = value.parent) { + const call = parent.type === 'arguments' ? parent.parent : null + if (parent.type === 'parenthesized_expression') + value = parent + else if (call?.type === 'call_expression' && WRAPPERS[calleeName(call)?.text ?? '']) + value = call + else + break + } + outer = value.parent?.type === 'variable_declarator' ? value.parent.parent : null + } + if (outer?.parent?.type === 'export_statement') outer = outer.parent - if (outer?.type === 'lexical_declaration' || outer?.type === 'variable_declaration' || outer?.type === 'function_declaration') - outer = outer.parent?.type === 'export_statement' ? outer.parent : outer const component = outer ? componentAt(outer) : undefined return component && sameNode(component.body, body) ? component : undefined }