Skip to content

Commit 70141f3

Browse files
author
朱锦涛
committed
feat(typescript-plugin): support go to definition for component props
- Add support for jumping from component attributes to child component's properties definition - Support Options API (createComponent/createPage/Component/Page) - Support Composition API (defineProps with generics and object syntax) - Support withDefaults(defineProps<Props>(), {...}) syntax - Support kebab-case to camelCase conversion (e.g., show-header → showHeader)
1 parent eb9d48a commit 70141f3

10 files changed

Lines changed: 1367 additions & 4 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Go to Definition 测试用例
2+
3+
本目录包含用于测试"跳转到定义"功能的测试用例。
4+
5+
## 测试场景
6+
7+
### 1. 组件属性跳转到 properties/props 定义
8+
9+
#### Options API 子组件 (`child-component.mpx`)
10+
11+
| 父组件属性 | 预期跳转目标 |
12+
| ------------- | ------------------------------- |
13+
| `title` | `properties.title` |
14+
| `count` | `properties.count` |
15+
| `show-header` | `properties.showHeader` |
16+
| `list-data` | `properties.listData` |
17+
| `config` | `properties.config` |
18+
| `name` | `properties.name` (简写形式) |
19+
| `age` | `properties.age` (简写形式) |
20+
| `visible` | `properties.visible` (简写形式) |
21+
22+
子组件中方法可能定义在不同位置:
23+
24+
| 父组件事件绑定的方法 | 子组件中定义位置 | 预期跳转目标 |
25+
| --------------------------- | -------------------- | ------------------------------- |
26+
| `onMethodsHandler` | methods 中 | `methods.onMethodsHandler` |
27+
| `onTopLevelHandler` | 顶层(和 data 同级) | `onTopLevelHandler()` |
28+
| `onTopLevelHandlerWithArgs` | 顶层(和 data 同级) | `onTopLevelHandlerWithArgs()` |
29+
| `onTopLevelArrowHandler` | 顶层(箭头函数) | `onTopLevelArrowHandler: () =>` |
30+
31+
#### Composition API 子组件 - 泛型写法 (`child-component-setup.mpx`)
32+
33+
| 父组件属性 | 预期跳转目标 |
34+
| ---------- | ------------------------------------ |
35+
| `message` | `defineProps<{ message: string }>` |
36+
| `visible` | `defineProps<{ visible: boolean }>` |
37+
| `items` | `defineProps<{ items: string[] }>` |
38+
| `optional` | `defineProps<{ optional?: string }>` |
39+
| `config` | `defineProps<{ config?: {...} }>` |
40+
41+
#### Composition API 子组件 - 对象写法 (`child-component-setup-object.mpx`)
42+
43+
| 父组件属性 | 预期跳转目标 |
44+
| ---------- | ----------------------------------- |
45+
| `msg` | `defineProps({ msg: String })` |
46+
| `count` | `defineProps({ count: Number })` |
47+
| `enabled` | `defineProps({ enabled: Boolean })` |
48+
49+
#### withDefaults 子组件 (`child-component-with-defaults.mpx`)
50+
51+
| 父组件属性 | 预期跳转目标 |
52+
| ---------- | ---------------------- |
53+
| `title` | `Props.title` 类型定义 |
54+
| `count` | `Props.count` 类型定义 |
55+
| `theme` | `Props.theme` 类型定义 |
56+
57+
### 2. 事件方法跳转
58+
59+
#### 支持的事件绑定语法
60+
61+
| 语法 | 示例 | 说明 |
62+
| ------------------- | ----------------------------- | --------------------- |
63+
| `bindxxx` | `bindtap="handler"` | 绑定事件 |
64+
| `bind:xxx` | `bind:tap="handler"` | 绑定事件(带冒号) |
65+
| `catchxxx` | `catchtap="handler"` | 捕获事件,阻止冒泡 |
66+
| `catch:xxx` | `catch:tap="handler"` | 捕获事件(带冒号) |
67+
| `capture-bind:xxx` | `capture-bind:tap="handler"` | 捕获阶段绑定 |
68+
| `capture-catch:xxx` | `capture-catch:tap="handler"` | 捕获阶段捕获 |
69+
| `mut-bind:xxx` | `mut-bind:tap="handler"` | 互斥事件绑定 (2.8.2+) |
70+
| 动态绑定 | `bindtap="{{ handlerName }}"` | 方法名是变量 |
71+
72+
#### Options API 父组件 (`parent-component.mpx`)
73+
74+
点击事件处理方法名,应跳转到对应的定义位置:
75+
76+
| 模板中的方法 | 定义位置 | 预期跳转目标 |
77+
| ---------------------- | ------------ | --------------------------------------------- |
78+
| `onChildChange` | methods | `methods.onChildChange` |
79+
| `handleViewTap` | methods | `methods.handleViewTap` |
80+
| `handleLongPress` | methods | `methods.handleLongPress` |
81+
| `inputBlur` | methods | `methods.inputBlur` |
82+
| `handleImageLoad` | methods | `methods.handleImageLoad` |
83+
| `setupHandler` | setup 返回值 | `setup() { return { setupHandler } }` |
84+
| `setupHandlerWithArgs` | setup 返回值 | `setup() { return { setupHandlerWithArgs } }` |
85+
| `dataHandler` | data | `data.dataHandler` |
86+
| `computedHandler` | computed | `computed.computedHandler` |
87+
88+
#### Composition API 父组件 (`parent-component-setup.mpx`)
89+
90+
点击事件处理方法名,应跳转到 `<script setup>` 中对应的函数定义:
91+
92+
| 模板中的方法 | 预期跳转目标 |
93+
| --------------- | --------------------------- |
94+
| `onChildChange` | `const onChildChange = ...` |
95+
| `handleViewTap` | `const handleViewTap = ...` |
96+
| `inputBlur` | `const inputBlur = ...` |
97+
98+
### 3. 特殊场景
99+
100+
#### 带参数的事件处理
101+
102+
```html
103+
<view bindtap="handleTapWithArgs('arg1', $event)"></view>
104+
```
105+
106+
点击 `handleTapWithArgs` 应跳转到方法定义。
107+
108+
#### 内联箭头函数
109+
110+
```html
111+
<view bindtap="{{ () => inlineHandler() }}"></view>
112+
```
113+
114+
点击 `inlineHandler` 应跳转到方法定义。
115+
116+
#### 属性名转换 (kebab-case -> camelCase)
117+
118+
```html
119+
<child-component show-header="{{ value }}" />
120+
```
121+
122+
点击 `show-header` 应跳转到子组件的 `properties.showHeader` 定义。
123+
124+
## 文件说明
125+
126+
| 文件 | 说明 |
127+
| ----------------------------------- | ---------------------------------------------- |
128+
| `child-component.mpx` | Options API 子组件,包含 properties 和 methods |
129+
| `child-component-setup.mpx` | Composition API 子组件,使用泛型 defineProps |
130+
| `child-component-setup-object.mpx` | Composition API 子组件,使用对象 defineProps |
131+
| `child-component-with-defaults.mpx` | Composition API 子组件,使用 withDefaults |
132+
| `parent-component.mpx` | Options API 父组件 (createComponent) |
133+
| `parent-component-setup.mpx` | Composition API 父组件 |
134+
| `parent-page.mpx` | Options API 页面 (createPage) |
135+
| `parent-page-setup.mpx` | Composition API 页面 |
136+
137+
## 页面 vs 组件
138+
139+
### 页面 (createPage)
140+
141+
页面使用 `createPage` 创建,有以下特点:
142+
143+
- 页面生命周期方法直接定义在顶层:`onLoad`, `onShow`, `onHide`, `onUnload`
144+
- 自定义方法也可以定义在顶层(和生命周期同级)
145+
- 也可以在 `methods` 中定义方法
146+
- 可以有 `properties` 接收页面参数
147+
148+
### 组件 (createComponent)
149+
150+
组件使用 `createComponent` 创建,有以下特点:
151+
152+
- 组件生命周期在 `lifetimes` 中定义
153+
- 方法通常在 `methods` 中定义
154+
- 也支持顶层定义方法(小程序原生写法)
155+
156+
## 如何测试
157+
158+
1. 在 VSCode 中打开 `parent-component.mpx``parent-component-setup.mpx`
159+
2. 按住 `Ctrl` (Windows/Linux) 或 `Cmd` (macOS) 并点击属性名或方法名
160+
3. 验证是否跳转到正确的定义位置
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<view>Child Component (Composition API - Object Style)</view>
3+
</template>
4+
5+
<script setup lang="ts">
6+
// defineProps 对象写法(类似 Options API)
7+
const { msg = 'default msg' } = defineProps({
8+
msg: String,
9+
count: Number,
10+
enabled: Boolean,
11+
data: Array,
12+
options: Object
13+
})
14+
15+
const handleClick = () => {
16+
console.log('click', msg)
17+
}
18+
19+
defineExpose({
20+
msg,
21+
handleClick
22+
})
23+
</script>
24+
25+
<script type="application/json">
26+
{
27+
"component": true
28+
}
29+
</script>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<template>
2+
<view>Child Component (Composition API)</view>
3+
<view bindtap="handleInternalTap">内部点击</view>
4+
</template>
5+
6+
<script setup lang="ts">
7+
// defineProps 泛型写法
8+
const props = defineProps<{
9+
message: string
10+
visible: boolean
11+
items: string[]
12+
// 可选属性
13+
optional?: string
14+
// 复杂类型
15+
config?: {
16+
theme: string
17+
size: number
18+
}
19+
}>()
20+
21+
// 组件内部方法
22+
const handleInternalTap = () => {
23+
console.log('internal tap', props.message)
24+
}
25+
26+
// 事件处理方法
27+
const onUpdate = (value: string) => {
28+
console.log('update:', value)
29+
}
30+
31+
const onClick = () => {
32+
console.log('click')
33+
}
34+
35+
const onCustomEvent = (data: { msg: string }) => {
36+
console.log('custom event:', data.msg)
37+
}
38+
39+
defineExpose({
40+
handleInternalTap,
41+
onUpdate,
42+
onClick,
43+
onCustomEvent
44+
})
45+
</script>
46+
47+
<script type="application/json">
48+
{
49+
"component": true
50+
}
51+
</script>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<template>
2+
<view>Child Component (withDefaults)</view>
3+
</template>
4+
5+
<script setup lang="ts">
6+
// withDefaults 写法
7+
type Props = {
8+
title?: string
9+
count?: number
10+
theme?: 'light' | 'dark'
11+
}
12+
13+
const props = withDefaults(defineProps<Props>(), {
14+
title: 'default title',
15+
count: 0,
16+
theme: 'light'
17+
})
18+
19+
const handleAction = () => {
20+
console.log('action', props.title)
21+
}
22+
23+
defineExpose({
24+
handleAction
25+
})
26+
</script>
27+
28+
<script type="application/json">
29+
{
30+
"component": true
31+
}
32+
</script>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<template>
2+
<view>Child Component (Options API)</view>
3+
</template>
4+
5+
<script lang="ts">
6+
import { createComponent } from '@mpxjs/core'
7+
8+
createComponent({
9+
properties: {
10+
// 基础类型属性 - 完整写法
11+
title: {
12+
type: String,
13+
value: 'default title'
14+
},
15+
count: {
16+
type: Number,
17+
value: 0
18+
},
19+
showHeader: {
20+
type: Boolean,
21+
value: true
22+
},
23+
// 复杂类型属性
24+
listData: {
25+
type: Array,
26+
value: [] as string[]
27+
},
28+
config: {
29+
type: Object,
30+
value: {} as Record<string, any>
31+
},
32+
// 简写形式
33+
name: String,
34+
age: Number,
35+
visible: Boolean
36+
}
37+
})
38+
</script>
39+
40+
<script type="application/json">
41+
{
42+
"component": true
43+
}
44+
</script>

0 commit comments

Comments
 (0)