From 6ce4ef7ee21ed6bc2da467c5e2149a425505e5fa Mon Sep 17 00:00:00 2001 From: cruldra Date: Mon, 5 Oct 2026 05:33:25 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(vscode):=20React=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=85=A7=E6=BA=90=E7=A0=81=E4=B8=80=E5=9D=97=E4=B8=80?= =?UTF-8?q?=E5=BC=A0=E5=8D=A1=E7=89=87=EF=BC=8C=E7=BB=84=E4=BB=B6=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E5=8F=AA=E7=95=99=E5=90=8D=E7=A7=B0=E3=80=81=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E3=80=81=E6=9E=84=E9=80=A0=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 组件体的语句不再并进组件卡片,照源码顺序是它的子块;状态、引用、数据源、派生值、副作用、事件处理函数、渲染输出各成一张卡片,回调和函数里的语句是这些卡片下面的子卡片 - 普通变量、提前返回的 if、注释照语言默认的卡片;表格格子里只写名字和位置,去掉「改它的」「来自」格子里单独打开源码的那条路 - 用词改成书面叫法(更新位置、使用位置、计算表达式、依赖项、触发时机、清理函数、绑定事件、样式取决于……),新增 toggle-right、anchor、download、sigma、layout-template 五个种类图标 - 夹具加上 luffy 的 QueuedBubble.tsx、PlanChecklist.tsx,规格的 React 一节照新样子改写 --- docs/superpowers/specs/code-reader/spec.md | 131 +-- vscode/src/codeReader/blockView.ts | 6 - vscode/src/codeReader/contract.ts | 45 +- .../codeReader/fixtures/planChecklist.tsx.txt | 148 ++++ .../codeReader/fixtures/queuedBubble.tsx.txt | 223 ++++++ vscode/src/codeReader/rules/react.test.ts | 546 +++++++++---- vscode/src/codeReader/rules/react.ts | 753 ++++++++---------- vscode/src/codeReader/rules/reactTree.test.ts | 72 +- vscode/src/codeReader/rules/reactTree.ts | 133 ++-- vscode/src/codeReader/rules/rustImpl.ts | 2 +- vscode/webview-ui/src/codeReader/App.tsx | 6 +- .../webview-ui/src/codeReader/BlockTree.tsx | 2 +- .../webview-ui/src/codeReader/CodeEditor.tsx | 34 +- .../src/codeReader/PropertyGrid.tsx | 100 +-- .../webview-ui/src/codeReader/SegmentText.tsx | 3 +- .../webview-ui/src/codeReader/codeReader.css | 5 +- .../webview-ui/src/codeReader/editing.test.ts | 20 - vscode/webview-ui/src/codeReader/editing.ts | 51 +- .../webview-ui/src/codeReader/kindIcons.tsx | 10 + vscode/webview-ui/src/codeReader/types.ts | 26 +- 20 files changed, 1327 insertions(+), 989 deletions(-) create mode 100644 vscode/src/codeReader/fixtures/planChecklist.tsx.txt create mode 100644 vscode/src/codeReader/fixtures/queuedBubble.tsx.txt diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index b9c21fd..91c38e6 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -1561,76 +1561,115 @@ export interface CommandRule { ### 内置:React 组件(TypeScript / JavaScript) -`vscode/src/codeReader/rules/react.ts`(读语法树的办法在 `rules/reactTree.ts`,都是纯函数),`languageIds: ['typescriptreact', 'javascriptreact']`,`libraries: []`(新的 JSX 写法不用导入 react,TSX / JSX 文件都启用)。原型:`code.js` 的「React 组件」一节(`TITLEBAR_CARD`、`LEGAL_CARD`、`reactCard`),插件菜单「React 组件」那一页的四张画板;样例是 luffy 的 `components/desktop/DesktopTitlebar.tsx`(`fixtures/desktopTitlebar.tsx.txt`,简单)、`components/LegalDocPage.tsx`(`fixtures/legalDocPage.tsx.txt`,复杂),说明板的例子 `DeleteAppDialog`、`DesktopSettingsDialog`、`AppVaultSummary`、`ShareButton`、`PosterDialog` 也复制进了 `fixtures/`。三种卡片: +`vscode/src/codeReader/rules/react.ts`(读语法树的办法在 `rules/reactTree.ts`,都是纯函数),`languageIds: ['typescriptreact', 'javascriptreact']`,`libraries: []`(新的 JSX 写法不用导入 react,TSX / JSX 文件都启用)。原型:`docs/prototype/code-reader/openpencil/react.js` 的三张画板「编辑器全貌」「卡片种类」「对照」(导出图 `exports/react-main.png`、`react-kinds.png`、`react-compare.png`);样例是 luffy 的 `chat/code/bubbles/QueuedBubble.tsx`(`fixtures/queuedBubble.tsx.txt`)和 `chat/code/PlanChecklist.tsx` 第 91–226 行(`fixtures/planChecklist.tsx.txt`,前面带上文件开头的导入),以前的 `DesktopTitlebar`、`LegalDocPage`、`DeleteAppDialog`、`DesktopSettingsDialog`、`AppVaultSummary`、`ShareButton`、`PosterDialog`、`OverviewSection` 也在 `fixtures/`。 -- 组件:种类 `react.component`,名字「React 组件」,图标 `atom`,颜色暗 #61dafb / 亮 #0a7ea4(组件色,渲染大纲里的组件、「客户端组件」标签同色),和函数块一样默认收起留下的子块。 -- 副作用:种类 `react.effect`,名字「副作用」,图标 `zap`,颜色暗 #e5c07b / 亮 #9a6a00。 -- 事件处理:种类 `react.handler`,名字「事件处理」,图标 `mouse-pointer-click`,颜色暗 #dcdcaa / 亮 #795e26。 +TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排在父卡片下面,缩进、竖线、折叠同函数体。规则只换掉 React 的几种写法,别的块(导入、接口、类型、常量、普通变量、if、注释、尝试……)照语言默认的卡片。组件卡片不把组件体并进来:组件体的语句照源码顺序是它的子块,照常交给各规则看。表格的格子里只写名字和位置,函数体只出现在子卡片里。 -认哪些是组件:名字大写开头;是函数声明、值是函数的变量(箭头函数、函数表达式,直接返回 JSX 的也算),或被 `memo(…)` / `forwardRef(…)`(`React.memo` 也算,可以套几层)包着的函数;函数自己的 return(嵌套函数里的不算)里有一处返回 JSX(元素、片段,三元、`&&` 的某一支是 JSX 也算;和 `null` 混着返回也算)。小写开头的工具函数、不返回 JSX 的大写函数照常是函数块。组件里又定义的组件自己也是一张组件卡片,不算处理函数。 +卡片种类(颜色写暗 / 亮): -整块换成组件卡片,不再是普通函数卡片的输入参数 / 输出参数。文件开头的 `"use client"`(文件里有组件时)和组件上面没并进块头部的 JSDoc(值是 `memo(…)`、直接返回 JSX 的常量块)略去(`RuleOmit`),讲成卡片上的「客户端组件」标签和「说明」。属性表(有才显示,没有就不画): +| 种类 id | 名字 | 图标 | 颜色 | 认哪一块 | 行 | +| --- | --- | --- | --- | --- | --- | +| `react.component` | React 组件 | `atom` | #61dafb / #0a7ea4 | 组件的声明 | 名称 / 说明 / 构造参数 | +| `react.state` | 状态 | `toggle-right` | #9cdcfe / #001080 | `const [v, setV] = useState(…)`、`useReducer(…)` | 名称 / 初始值 / 更新位置 | +| `react.ref` | 引用 | `anchor` | #9cdcfe / #001080 | `useRef(…)` | 名称 / 初始值 / 使用位置 | +| `react.source` | 数据源 | `download` | #9cdcfe / #001080 | 别的钩子赋给变量(`useQuery`、`usePathname`、`useRouter`、自定义 `useXxx`) | 变量 / 钩子 / 说明 | +| `react.derived` | 派生值 | `sigma` | #9cdcfe / #001080 | `useMemo`、`useCallback` | 名称 / 计算表达式 / 依赖项 | +| `react.effect` | 副作用 | `zap` | #e5c07b / #9a6a00 | `useEffect`、`useLayoutEffect`、`useInsertionEffect` | 触发时机 / 清理函数 | +| `react.handler` | 事件处理函数 | `mouse-pointer-click` | #dcdcaa / #795e26 | 组件体里定义的函数(函数声明、值是函数的常量) | 名称 / 参数 / 绑定事件 | +| `react.render` | 渲染输出 | `layout-template` | #89d185 / #388a34 | 组件函数自己返回 JSX 的 return | JSX 结构大纲 | + +组件色同时用在渲染大纲里的组件、「客户端组件」标签上;状态、引用、数据源、派生值都是数据,共用变量块的浅蓝,靠图标区分;副作用同「选项」块的黄,事件处理函数同「函数」块,渲染输出同「返回」块的绿。筛选栏的「种类」按这几个 id 筛,叫上表的名字。 + +认哪些是组件:名字大写开头;是函数声明、值是函数的变量(箭头函数、函数表达式,直接返回 JSX 的也算),或被 `memo(…)` / `forwardRef(…)`(`React.memo` 也算,可以套几层)包着的函数;函数自己的 return(嵌套函数里的不算)里有一处返回 JSX(元素、片段,三元、`&&` 的某一支是 JSX 也算;和 `null` 混着返回也算)。小写开头的工具函数、不返回 JSX 的大写函数照常是函数块。组件里又定义的组件自己也是一张组件卡片,不算事件处理函数。 + +认领(`LibraryRule.claim` 一块一块看,组件卡片的 `absorbs` 是空的): + +- 组件块(函数块、只声明一个变量的常量 / 变量块)是组件时换成组件卡片。文件开头的 `"use client"`(文件里有组件时)和组件上面没并进块头部的 JSDoc(值是 `memo(…)`、直接返回 JSX 的常量块)略去(`RuleOmit`),讲成卡片上的「客户端组件」标签和「说明」。 +- 组件体里的块:语法树往上找,块的语句直接是某个组件函数主体(语句块)的子语句才认(`enclosingComponent`:所在的函数经 `componentOfFunction` 找回它的声明,再用认组件的 `componentAt` 核对),按组件体的归类(`bodyOf`)认成状态、引用、数据源、派生值、副作用、事件处理函数。 +- `return` 块:所在的最内层函数(`enclosingFunction`)是组件函数、返回的是 JSX 时认成渲染输出;写在 `if` 里的提前返回 JSX 也算,它是如果卡片下面的子块。返回 `null` 这类的照常是返回块。 +- 别的照语言默认:组件体里普通的 `const x = 表达式` 是变量卡片,`if (…) return null` 是如果卡片、下面是返回卡片,注释照常成块(不再并进卡片)。 +- 回调、函数里的语句来自 TypeScript 语言插件本来给的子块:调用的实参是带语句块的函数时(任何调用都一样),那句语句、那个变量的子块就是函数里的语句,所以 `useEffect(() => {…})`、`useCallback(() => {…})`、`useMemo(() => {…})` 的语句排在副作用、派生值卡片下面;事件处理函数是函数块,函数体照常是子块。 + +组件卡片: | 行 | 内容 | | --- | --- | | 名称 | 组件名(组件色,带下标);后面跟小标签(`tag` 框):「具名导出」「默认导出」(同 import / export 关键字的颜色;`export default X`、`export { X }` 写在文件别处也算)、「客户端组件」(组件色)、「记忆化」「转发 ref」(同 const 关键字的颜色,memo / forwardRef 包着) | | 说明 | 组件上面的 JSDoc,Markdown | -| 构造参数 | `N 个`(hint);属性类型写的是一个名字(接口、类型别名、导入的类型)时后面写「 · 来自 XxxProps」,名字带下标(⌘ + 点击跳到类型)。下面一张表格「名称 \| 类型 \| 默认值 \| 必填」,有属性带说明时多一列「说明」。没写参数时写「无」 | -| 状态 | useState / useReducer:「名称 \| 初始值 \| 改它的」 | -| 引用 | useRef:「名称 \| 初始值 \| 用在」 | -| 读取的数据 | 别的钩子(`useXxx(…)` 赋给变量):「变量 \| 来自 \| 意思」 | -| 派生值 | 组件体里别的常量,和 useMemo / useCallback:「名称 \| 怎么算 \| 什么时候算」 | -| 副作用 | `N 个`(hint),下面每个 useEffect / useLayoutEffect / useInsertionEffect 一张副作用子卡片 | -| 事件处理 | 组件体里定义的函数(函数声明、值是函数的常量):「名称 \| 绑定在 \| 做什么」,点名称在表下面展开它的子卡片 | -| 渲染 | `N 处返回`(hint),下面是返回的 JSX 画成的结构大纲 | +| 构造参数 | `N 个`(hint);属性类型写的是一个名字(接口、类型别名、导入的类型)时后面写「 · 类型 XxxProps」,名字带下标(⌘ + 点击跳到类型)。下面一张表格「名称 \| 类型 \| 默认值 \| 必填」,有属性带说明时多一列「说明」。没写参数时写「无」 | -构造参数:属性类型的字段照类型里的顺序一行一个(类型字面量、同文件的接口 / 类型别名,交叉类型各部分接起来,`Readonly<…>`、`PropsWithChildren<…>` 看里面;没标注参数类型时 `const X: FC` 的泛型实参、`forwardRef` 的第二个泛型实参),解构里有、类型里没有的接在后面,`...rest` 最后(类型写「其余的属性」)。名称是属性名(定义处带下标:解构出来的是解构那处,没解构的是字段;跳到定义只闪这一行),`children` 后面标「子元素」、函数类型的属性(`(x) => R`、方法签名)标「回调」(组件色的小标签),解构时改了名写「(组件里叫 x)」;类型照翻译插件翻,方法签名写成函数签名;默认值取解构的默认值,没有写「—」;必填看类型的 `?`(是 / 否,storage 色),没类型可看时写了默认值的「否」、别的「—」;说明是字段上面的 JSDoc / 注释、这一行行尾的注释(comment 色,几行接成一行)。类型是导入的名字时经工作区索引查它的字段(索引 `react.propTypes`:看工作区里所有 `**/*.{ts,tsx}`,预筛有 `interface X` / `type X` 才解析,按 typescriptreact 解析;从文件顶层认出接口、类型别名,键是名字、值是各字段的名字、类型写法、可省略、回调、说明的 JSON),查法同「规则认领时查别的文件里的定义」:先照解构的出行、类型「未标注」,查到了按字段重排、重发;别的文件里的类型照写文字(type 色)。 +直接返回 JSX 的箭头函数组件(`const Tag = ({ text }) => {text}`)没有子块可放,它渲染的大纲作为第四行「JSX 结构大纲」写在组件卡片里。 -状态、引用、读取的数据、派生值: +构造参数:属性类型的字段照类型里的顺序一行一个(类型字面量、同文件的接口 / 类型别名,交叉类型各部分接起来,`Readonly<…>`、`PropsWithChildren<…>` 看里面;没标注参数类型时 `const X: FC` 的泛型实参、`forwardRef` 的第二个泛型实参),解构里有、类型里没有的接在后面,`...rest` 最后(类型写「其余的属性」)。名称是属性名(定义处带下标:解构出来的是解构那处,没解构的是字段;跳到定义只闪这一行),`children` 后面标「子元素」、函数类型的属性(`(x) => R`、方法签名)标「回调」(组件色的小标签),解构时改了名写「(组件内名为 x)」;类型照翻译插件翻,方法签名写成函数签名;默认值取解构的默认值,没有写「—」;必填看类型的 `?`(是 / 否,storage 色),没类型可看时写了默认值的「否」、别的「—」;说明是字段上面的 JSDoc / 注释、这一行行尾的注释(comment 色,几行接成一行)。类型是导入的名字时经工作区索引查它的字段(索引 `react.propTypes`:看工作区里所有 `**/*.{ts,tsx}`,预筛有 `interface X` / `type X` 才解析,按 typescriptreact 解析;从文件顶层认出接口、类型别名,键是名字、值是各字段的名字、类型写法、可省略、回调、说明的 JSON),查法同「规则认领时查别的文件里的定义」:先照解构的出行、类型「未标注」,查到了按字段重排、重发;别的文件里的类型照写文字(type 色)。 -- 名称、变量带下标(解构模式照常翻),跳到定义只闪这一行。 -- 状态的初始值:useState 的实参、useReducer 的第二个实参(后面括号里写 reducer);改它的:改状态的函数名(function 色,带下标),后面括号里写在哪儿改,「;」隔开:「副作用」(几个时「副作用 2」)、处理函数名、派生值名、「 的 onChange(传 e.target.value)」(绑在 JSX 属性上;在列表里一项项渲染的前面加「每个」)、「渲染时,当 … 时」(渲染时按条件重置状态的 `if (…) { setX(…) }`:主体都是在调改状态的函数,这句并进卡片)。 -- 引用的用在:「挂在
的 ref 上」(元素写成 ``:组件用组件色、能跳到定义(`refers` 到标签名),原生标签灰色,带 `aria-label` / `title` 字符串时后面写「(返回)」),别处用到的「副作用 1、handleSectionClick 里用它」。 -- 读取的数据的来自照常翻;意思:库自带的钩子照词表(next/navigation 的 `usePathname` 当前路径、`useRouter` 路由器,跳转用、`useSearchParams`、`useParams`、`useSelectedLayoutSegment(s)`,react 的 `useId`、`useTransition`、`useSyncExternalStore`、`useOptimistic`、`useActionState`、`useDeferredValue`,react-dom 的 `useFormStatus`);`useContext(X)` 是「从 X 上下文读」;传选择器的 `useXxx((s) => s.a.b)` 是「从 xxx store 读 a.b」(store 名:导入路径最后一节是 store 时取上一节,`@/lib/desktop/store` 是 desktop;否则去掉 store、use 字样,再不行取钩子名),选择器不是直接取属性的写「从 xxx store 算 …」;自己项目里的(相对路径、`@/` 这类别名、同文件)是「自定义钩子」,别的库的是「xxx 的钩子」(hint)。 -- 派生值的怎么算照常翻:useMemo 是算值的那个表达式,useCallback 写「函数 」加函数;主体是语句块时写「N 句」,那句照常是块、放进这一行对应的子行上,点名称展开(useCallback 的用事件处理子卡片,useMemo 的照常是变量块);什么时候算:普通常量「每次渲染」,useMemo / useCallback 按依赖读(同副作用的时机)、后面写「(useMemo)」。 +状态、引用、数据源、派生值(名称、变量带下标,解构模式照常翻;跳到定义闪这张卡片): -副作用子卡片(组件体里那句 `useEffect(…)` 照常是块,被规则认成这张卡片,放进卡片「副作用」那一行下面):时机按依赖数组读,`[a, b]` 是「a、b 变了时」,`[]` 是「只在挂载时」,没写是「每次渲染后」(keyword 色),useLayoutEffect 后面加「,浏览器绘制前」;做什么是紧贴在上面的注释(几行接成一行,hint 色;这段注释并进组件卡片),没写注释时写「N 句」;副作用函数里的语句照常是块,放进「做什么」那一行下面;最后返回清理函数时那句 return 并进来,多一行「清理」:`() => …` 翻它的主体,语句块一句句翻、「;」隔开。 +- 状态的初始值:useState 的实参、useReducer 的第二个实参(后面括号里写 reducer)。更新位置:调用改状态函数的各处,只写名字和位置,「、」隔开,写出来一样的只留一处,没有时「—」:「副作用」(几个时「副作用 2」,副作用色)、事件处理函数名 / 派生值名 / 变量名 / 数据源的变量名(带下标,⌘ + 点击跳到那张卡片)、「