schema-dsl 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -2
- package/docs/error-handling.md +928 -2
- package/index.js +89 -4
- package/package.json +1 -1
- package/docs/typescript-never-in-try-catch.md +0 -298
- package/test-try-catch-scenario.ts +0 -183
package/index.js
CHANGED
|
@@ -203,6 +203,79 @@ function getDefaultValidator() {
|
|
|
203
203
|
return _defaultValidator;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* 智能类型转换:只转换字符串→数字(当schema要求number且能转换时)
|
|
208
|
+
* @private
|
|
209
|
+
* @param {*} data - 原始数据
|
|
210
|
+
* @param {Object} schema - JSON Schema对象
|
|
211
|
+
* @returns {*} 转换后的数据
|
|
212
|
+
*/
|
|
213
|
+
function smartCoerceTypes(data, schema) {
|
|
214
|
+
if (!data || typeof data !== 'object') return data;
|
|
215
|
+
|
|
216
|
+
// 获取 schema 对象
|
|
217
|
+
const schemaObj = schema.toSchema ? schema.toSchema() : schema;
|
|
218
|
+
const properties = schemaObj.properties || {};
|
|
219
|
+
|
|
220
|
+
// 处理数组
|
|
221
|
+
if (Array.isArray(data)) {
|
|
222
|
+
return data.map(item => smartCoerceTypes(item, schema));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 处理对象
|
|
226
|
+
const result = { ...data };
|
|
227
|
+
|
|
228
|
+
Object.keys(result).forEach(key => {
|
|
229
|
+
const value = result[key];
|
|
230
|
+
const fieldSchema = properties[key];
|
|
231
|
+
|
|
232
|
+
if (!fieldSchema) return;
|
|
233
|
+
|
|
234
|
+
// ⚠️ 关键修复:如果字段有 enum 约束,不进行类型转换
|
|
235
|
+
// 原因:枚举验证需要严格匹配类型
|
|
236
|
+
// 例如:数字枚举 [1,2,3] 不应该接受字符串 "1"
|
|
237
|
+
if (fieldSchema.enum) {
|
|
238
|
+
return; // 跳过枚举字段的转换
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 核心规则:只有同时满足以下三个条件才转换
|
|
242
|
+
// 1. 值是字符串
|
|
243
|
+
// 2. Schema 要求 number 类型
|
|
244
|
+
// 3. 能正常转换为有效数字
|
|
245
|
+
// 4. 不是枚举字段(已在上面检查)
|
|
246
|
+
if (fieldSchema.type === 'number' && typeof value === 'string') {
|
|
247
|
+
const trimmed = value.trim();
|
|
248
|
+
if (trimmed !== '') {
|
|
249
|
+
const num = Number(trimmed);
|
|
250
|
+
if (!isNaN(num)) {
|
|
251
|
+
result[key] = num;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// 处理嵌套对象
|
|
256
|
+
else if (fieldSchema.type === 'object' && typeof value === 'object' && value !== null) {
|
|
257
|
+
result[key] = smartCoerceTypes(value, fieldSchema);
|
|
258
|
+
}
|
|
259
|
+
// 处理数组元素
|
|
260
|
+
else if (fieldSchema.type === 'array' && Array.isArray(value)) {
|
|
261
|
+
if (fieldSchema.items && fieldSchema.items.type === 'number') {
|
|
262
|
+
result[key] = value.map(item => {
|
|
263
|
+
if (typeof item === 'string') {
|
|
264
|
+
const trimmed = item.trim();
|
|
265
|
+
if (trimmed !== '') {
|
|
266
|
+
const num = Number(trimmed);
|
|
267
|
+
return !isNaN(num) ? num : item;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return item;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
206
279
|
/**
|
|
207
280
|
* 便捷验证方法(使用默认Validator)
|
|
208
281
|
* @param {Object} schema - JSON Schema对象
|
|
@@ -211,6 +284,7 @@ function getDefaultValidator() {
|
|
|
211
284
|
* @param {boolean} [options.format=true] - 是否格式化错误
|
|
212
285
|
* @param {string} [options.locale] - 动态指定语言(如 'zh-CN', 'en-US')
|
|
213
286
|
* @param {Object} [options.messages] - 自定义错误消息
|
|
287
|
+
* @param {boolean} [options.coerce=true] - 是否启用智能类型转换(字符串→数字)
|
|
214
288
|
* @returns {Object} 验证结果
|
|
215
289
|
*
|
|
216
290
|
* @example
|
|
@@ -218,13 +292,24 @@ function getDefaultValidator() {
|
|
|
218
292
|
*
|
|
219
293
|
* const schema = dsl({ email: 'email!' });
|
|
220
294
|
*
|
|
221
|
-
* //
|
|
222
|
-
* const result1 = validate(schema, {
|
|
295
|
+
* // 基本验证(默认启用智能转换)
|
|
296
|
+
* const result1 = validate(schema, { userId: '123', age: '25' });
|
|
297
|
+
* // userId 和 age 自动转为数字
|
|
298
|
+
*
|
|
299
|
+
* // 禁用智能转换
|
|
300
|
+
* const result2 = validate(schema, data, { coerce: false });
|
|
223
301
|
*
|
|
224
302
|
* // 指定语言
|
|
225
|
-
* const
|
|
303
|
+
* const result3 = validate(schema, { email: 'invalid' }, { locale: 'zh-CN' });
|
|
226
304
|
*/
|
|
227
|
-
function validate(schema, data, options) {
|
|
305
|
+
function validate(schema, data, options = {}) {
|
|
306
|
+
// 默认启用智能转换(只转换字符串→数字)
|
|
307
|
+
const shouldCoerce = options.coerce !== false;
|
|
308
|
+
|
|
309
|
+
if (shouldCoerce) {
|
|
310
|
+
data = smartCoerceTypes(data, schema);
|
|
311
|
+
}
|
|
312
|
+
|
|
228
313
|
return getDefaultValidator().validate(schema, data, options);
|
|
229
314
|
}
|
|
230
315
|
|
package/package.json
CHANGED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
# TypeScript never 类型在 try-catch 中的行为分析
|
|
2
|
-
|
|
3
|
-
## 问题描述
|
|
4
|
-
|
|
5
|
-
当在 `try-catch` 的 `catch` 块中使用 `dsl.error.throw()` 时,TypeScript 可能会报错或者类型推断不正确。
|
|
6
|
-
|
|
7
|
-
## 原因分析
|
|
8
|
-
|
|
9
|
-
### 1. TypeScript 的控制流分析
|
|
10
|
-
|
|
11
|
-
TypeScript 的控制流分析(Control Flow Analysis)对 `try-catch` 块有特殊处理:
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
function test(): string {
|
|
15
|
-
try {
|
|
16
|
-
return 'value';
|
|
17
|
-
} catch (error) {
|
|
18
|
-
// 在 catch 块中,TypeScript 需要确定:
|
|
19
|
-
// 1. 这个块是否会正常返回?
|
|
20
|
-
// 2. 这个块是否会抛出异常?
|
|
21
|
-
// 3. 这个块是否永不返回(never)?
|
|
22
|
-
}
|
|
23
|
-
// 这里:TypeScript 需要判断是否可达
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 2. never 类型的特殊性
|
|
28
|
-
|
|
29
|
-
`never` 类型表示"永不返回",但在 `try-catch` 中,TypeScript 的推断可能受限:
|
|
30
|
-
|
|
31
|
-
**场景A - 直接调用(✅ 正常工作)**:
|
|
32
|
-
```typescript
|
|
33
|
-
function directCall(id: string | null) {
|
|
34
|
-
if (!id) {
|
|
35
|
-
dsl.error.throw('error'); // TypeScript 知道这里 never 返回
|
|
36
|
-
}
|
|
37
|
-
console.log(id.toUpperCase()); // ✅ id 被收窄为 string
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**场景B - 在 catch 中调用(⚠️ 可能有问题)**:
|
|
42
|
-
```typescript
|
|
43
|
-
function inCatch(id: string | null): string {
|
|
44
|
-
try {
|
|
45
|
-
if (!id) throw new Error();
|
|
46
|
-
return id.toUpperCase();
|
|
47
|
-
} catch (error) {
|
|
48
|
-
dsl.error.throw('error'); // ⚠️ TypeScript 可能不确定这里 never 返回
|
|
49
|
-
}
|
|
50
|
-
// ❌ 这里可能被认为是可达的
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### 3. 为什么添加 unreachable 保证有效?
|
|
55
|
-
|
|
56
|
-
我们的修复方案(添加第二个 throw)帮助 TypeScript 更明确地理解函数行为:
|
|
57
|
-
|
|
58
|
-
**修复前**:
|
|
59
|
-
```javascript
|
|
60
|
-
static throw(code, paramsOrLocale, statusCode, locale) {
|
|
61
|
-
throw new I18nError(...);
|
|
62
|
-
// TypeScript: "函数体结束了,但我不确定是否永不返回"
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**修复后**:
|
|
67
|
-
```javascript
|
|
68
|
-
static throw(code, paramsOrLocale, statusCode, locale) {
|
|
69
|
-
throw new I18nError(...);
|
|
70
|
-
// eslint-disable-next-line no-unreachable
|
|
71
|
-
throw new Error('unreachable');
|
|
72
|
-
// TypeScript: "哦,有两个 throw,这个函数确实永不返回"
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## 测试结果
|
|
77
|
-
|
|
78
|
-
### 当前版本 (v1.2.0) - 所有场景通过 ✅
|
|
79
|
-
|
|
80
|
-
经过测试,以下所有场景都**不再报错**:
|
|
81
|
-
|
|
82
|
-
| 场景 | 描述 | 结果 |
|
|
83
|
-
|------|------|------|
|
|
84
|
-
| scenario1 | 直接使用 throw | ✅ 通过 |
|
|
85
|
-
| scenario2 | catch 块中使用 throw(无返回值) | ✅ 通过 |
|
|
86
|
-
| scenario3 | catch 块中使用 throw(有返回类型) | ✅ 通过 |
|
|
87
|
-
| scenario4 | catch 块中 throw 后有其他代码 | ✅ 通过 |
|
|
88
|
-
| scenario5 | 使用类型断言 | ✅ 通过 |
|
|
89
|
-
| scenario6 | 多层 try-catch 嵌套 | ✅ 通过 |
|
|
90
|
-
| scenario7 | 普通 throw 对比 | ✅ 通过 |
|
|
91
|
-
| scenario8 | 返回 never 函数 | ✅ 通过 |
|
|
92
|
-
| scenario9 | 严格模式无 return | ✅ 通过 |
|
|
93
|
-
| scenario10 | catch 后有代码块 | ✅ 通过 |
|
|
94
|
-
| scenario11 | 条件分支中的 throw | ✅ 通过 |
|
|
95
|
-
| scenario12 | 类型收窄测试 | ✅ 通过 |
|
|
96
|
-
|
|
97
|
-
## 为什么会报错(理论情况)
|
|
98
|
-
|
|
99
|
-
在某些 TypeScript 配置或版本下,可能出现以下报错:
|
|
100
|
-
|
|
101
|
-
### 错误1: "Function lacks ending return statement"
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
function test(): string {
|
|
105
|
-
try {
|
|
106
|
-
return 'value';
|
|
107
|
-
} catch (error) {
|
|
108
|
-
dsl.error.throw('error');
|
|
109
|
-
}
|
|
110
|
-
// TS2366: Function lacks ending return statement and return type does not include 'undefined'.
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**原因**: TypeScript 不确定 catch 块是否永不返回
|
|
115
|
-
|
|
116
|
-
### 错误2: "Not all code paths return a value"
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
function test(): string {
|
|
120
|
-
try {
|
|
121
|
-
return 'value';
|
|
122
|
-
} catch (error) {
|
|
123
|
-
dsl.error.throw('error');
|
|
124
|
-
}
|
|
125
|
-
console.log('after catch');
|
|
126
|
-
// TS7030: Not all code paths return a value.
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
**原因**: TypeScript 认为 catch 块可能正常结束,导致后面的代码可达
|
|
131
|
-
|
|
132
|
-
### 错误3: 类型收窄失效
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
function test(value: string | null) {
|
|
136
|
-
try {
|
|
137
|
-
if (!value) {
|
|
138
|
-
throw new Error();
|
|
139
|
-
}
|
|
140
|
-
return value.toUpperCase();
|
|
141
|
-
} catch (error) {
|
|
142
|
-
dsl.error.throw('error');
|
|
143
|
-
}
|
|
144
|
-
// value 在这里可能仍然是 string | null,而不是 string
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**原因**: TypeScript 的控制流分析在 try-catch 边界可能失效
|
|
149
|
-
|
|
150
|
-
## 解决方案对比
|
|
151
|
-
|
|
152
|
-
### 方案1: 我们的修复(推荐)✅
|
|
153
|
-
|
|
154
|
-
```javascript
|
|
155
|
-
// lib/errors/I18nError.js
|
|
156
|
-
static throw(code, paramsOrLocale, statusCode, locale) {
|
|
157
|
-
throw new I18nError(code, params, actualStatusCode, actualLocale);
|
|
158
|
-
// eslint-disable-next-line no-unreachable
|
|
159
|
-
throw new Error('unreachable');
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**优点**:
|
|
164
|
-
- ✅ 修复了类型系统问题
|
|
165
|
-
- ✅ 不影响运行时行为
|
|
166
|
-
- ✅ 不需要用户修改代码
|
|
167
|
-
|
|
168
|
-
### 方案2: 用户使用类型断言(临时方案)
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
function test(): string {
|
|
172
|
-
try {
|
|
173
|
-
return 'value';
|
|
174
|
-
} catch (error) {
|
|
175
|
-
dsl.error.throw('error');
|
|
176
|
-
return undefined as never; // 类型断言
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**缺点**:
|
|
182
|
-
- ❌ 需要用户每次都写
|
|
183
|
-
- ❌ 代码不优雅
|
|
184
|
-
- ❌ 容易忘记
|
|
185
|
-
|
|
186
|
-
### 方案3: 使用 return
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
function test(): string {
|
|
190
|
-
try {
|
|
191
|
-
return 'value';
|
|
192
|
-
} catch (error) {
|
|
193
|
-
return dsl.error.throw('error'); // return 一个 never
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**缺点**:
|
|
199
|
-
- ❌ 语义不清晰(return 一个永不返回的函数?)
|
|
200
|
-
- ❌ 用户需要记住这个用法
|
|
201
|
-
|
|
202
|
-
### 方案4: 不使用 try-catch(规避)
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
function test(id: string | null): string {
|
|
206
|
-
if (!id) {
|
|
207
|
-
dsl.error.throw('error'); // ✅ 这样就没问题
|
|
208
|
-
}
|
|
209
|
-
return id.toUpperCase();
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
**缺点**:
|
|
214
|
-
- ❌ 限制了用户的代码结构
|
|
215
|
-
- ❌ 不适用于需要捕获异常的场景
|
|
216
|
-
|
|
217
|
-
## TypeScript 版本兼容性
|
|
218
|
-
|
|
219
|
-
| TypeScript 版本 | 修复前 | 修复后 |
|
|
220
|
-
|----------------|--------|--------|
|
|
221
|
-
| 4.5+ | ⚠️ 可能报错 | ✅ 正常 |
|
|
222
|
-
| 4.0-4.4 | ⚠️ 可能报错 | ✅ 正常 |
|
|
223
|
-
| 3.x | ⚠️ 可能报错 | ✅ 正常 |
|
|
224
|
-
|
|
225
|
-
## 实际使用建议
|
|
226
|
-
|
|
227
|
-
### ✅ 推荐用法
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
// 1. 直接在条件分支中使用(最推荐)
|
|
231
|
-
function test1(id: string | null) {
|
|
232
|
-
if (!id) {
|
|
233
|
-
dsl.error.throw('error');
|
|
234
|
-
}
|
|
235
|
-
return id.toUpperCase();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// 2. 在 catch 块中使用(v1.2.0+ 支持)
|
|
239
|
-
function test2(): string {
|
|
240
|
-
try {
|
|
241
|
-
return processData();
|
|
242
|
-
} catch (error) {
|
|
243
|
-
dsl.error.throw('processing.failed'); // ✅ 正常工作
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// 3. 使用 assert(推荐)
|
|
248
|
-
function test3(account: any) {
|
|
249
|
-
dsl.error.assert(account, 'account.notFound');
|
|
250
|
-
return account.balance; // account 已被收窄为 truthy
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### ⚠️ 注意事项
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
// 1. 条件分支中的 throw(需要确保所有分支都覆盖)
|
|
258
|
-
function test(shouldThrow: boolean): string {
|
|
259
|
-
try {
|
|
260
|
-
return 'value';
|
|
261
|
-
} catch (error) {
|
|
262
|
-
if (shouldThrow) {
|
|
263
|
-
dsl.error.throw('error');
|
|
264
|
-
}
|
|
265
|
-
return 'fallback'; // ⚠️ 这个 return 是必需的
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// 2. finally 块中不要使用 throw
|
|
270
|
-
function test(): string {
|
|
271
|
-
try {
|
|
272
|
-
return 'value';
|
|
273
|
-
} catch (error) {
|
|
274
|
-
dsl.error.throw('error');
|
|
275
|
-
} finally {
|
|
276
|
-
// ❌ 不要在这里使用 throw
|
|
277
|
-
// dsl.error.throw('finally.error');
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
## 总结
|
|
283
|
-
|
|
284
|
-
1. **问题根源**: TypeScript 的控制流分析在 try-catch 中对 never 类型的推断可能不够准确
|
|
285
|
-
|
|
286
|
-
2. **修复方案**: 在 throw 方法末尾添加 `throw new Error('unreachable')`,明确告诉 TypeScript 这个函数永不返回
|
|
287
|
-
|
|
288
|
-
3. **修复效果**: v1.2.0 版本后,所有测试场景都通过,不再报错
|
|
289
|
-
|
|
290
|
-
4. **用户影响**: 用户无需修改任何代码,升级到 v1.2.0 即可解决问题
|
|
291
|
-
|
|
292
|
-
5. **向后兼容**: 完全向后兼容,不影响现有功能
|
|
293
|
-
|
|
294
|
-
## 参考资料
|
|
295
|
-
|
|
296
|
-
- [TypeScript Control Flow Analysis](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)
|
|
297
|
-
- [TypeScript never Type](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type)
|
|
298
|
-
- [TypeScript Issue #8655](https://github.com/microsoft/TypeScript/issues/8655) - never in try-catch
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 测试场景:在 try-catch 中使用 throw 方法
|
|
3
|
-
* 验证 TypeScript 类型推断是否正确
|
|
4
|
-
*/
|
|
5
|
-
import { dsl } from './index';
|
|
6
|
-
|
|
7
|
-
// ===== 场景1:直接在函数中使用 throw(正常工作)=====
|
|
8
|
-
function scenario1_direct(id: string | null) {
|
|
9
|
-
if (!id) {
|
|
10
|
-
dsl.error.throw('account.notFound');
|
|
11
|
-
// ✅ TypeScript 知道这里永不返回
|
|
12
|
-
}
|
|
13
|
-
// ✅ TypeScript 知道 id 一定不是 null
|
|
14
|
-
console.log(id.toUpperCase());
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ===== 场景2:在 try-catch 的 catch 块中使用 throw =====
|
|
18
|
-
function scenario2_inCatch(id: string | null) {
|
|
19
|
-
try {
|
|
20
|
-
// 一些可能抛错的操作
|
|
21
|
-
if (!id) {
|
|
22
|
-
throw new Error('id is null');
|
|
23
|
-
}
|
|
24
|
-
return id.toUpperCase();
|
|
25
|
-
} catch (error) {
|
|
26
|
-
// ⚠️ 在 catch 块中调用 dsl.error.throw
|
|
27
|
-
dsl.error.throw('account.notFound');
|
|
28
|
-
// ❌ 问题:TypeScript 可能不知道这里永不返回
|
|
29
|
-
}
|
|
30
|
-
// ❌ TypeScript 可能认为这里可达,报错:"Not all code paths return a value"
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ===== 场景3:在 catch 块中使用 throw,有返回类型声明 =====
|
|
34
|
-
function scenario3_withReturnType(id: string | null): string {
|
|
35
|
-
try {
|
|
36
|
-
if (!id) {
|
|
37
|
-
throw new Error('id is null');
|
|
38
|
-
}
|
|
39
|
-
return id.toUpperCase();
|
|
40
|
-
} catch (error) {
|
|
41
|
-
dsl.error.throw('account.notFound');
|
|
42
|
-
}
|
|
43
|
-
// ❌ TypeScript 会报错:"Function lacks ending return statement"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ===== 场景4:在 catch 块中先 throw,再有其他代码 =====
|
|
47
|
-
function scenario4_codeAfterThrow(id: string | null): string {
|
|
48
|
-
try {
|
|
49
|
-
if (!id) {
|
|
50
|
-
throw new Error('id is null');
|
|
51
|
-
}
|
|
52
|
-
return id.toUpperCase();
|
|
53
|
-
} catch (error) {
|
|
54
|
-
dsl.error.throw('account.notFound');
|
|
55
|
-
console.log('这行代码永远不会执行'); // ⚠️ 但 TypeScript 可能认为会执行
|
|
56
|
-
return 'default'; // ⚠️ TypeScript 可能认为这是有效的返回
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ===== 场景5:使用类型断言(workaround)=====
|
|
61
|
-
function scenario5_withAssertion(id: string | null): string {
|
|
62
|
-
try {
|
|
63
|
-
if (!id) {
|
|
64
|
-
throw new Error('id is null');
|
|
65
|
-
}
|
|
66
|
-
return id.toUpperCase();
|
|
67
|
-
} catch (error) {
|
|
68
|
-
dsl.error.throw('account.notFound');
|
|
69
|
-
return undefined as never; // 类型断言 workaround
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ===== 场景6:多层 try-catch 嵌套 =====
|
|
74
|
-
function scenario6_nested(id: string | null): string {
|
|
75
|
-
try {
|
|
76
|
-
try {
|
|
77
|
-
if (!id) {
|
|
78
|
-
throw new Error('id is null');
|
|
79
|
-
}
|
|
80
|
-
return id.toUpperCase();
|
|
81
|
-
} catch (innerError) {
|
|
82
|
-
dsl.error.throw('account.notFound');
|
|
83
|
-
}
|
|
84
|
-
} catch (outerError) {
|
|
85
|
-
return 'fallback';
|
|
86
|
-
}
|
|
87
|
-
// ❌ TypeScript 可能报错
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ===== 场景7:对比 - 使用普通 throw(正常工作)=====
|
|
91
|
-
function scenario7_normalThrow(id: string | null): string {
|
|
92
|
-
try {
|
|
93
|
-
if (!id) {
|
|
94
|
-
throw new Error('id is null');
|
|
95
|
-
}
|
|
96
|
-
return id.toUpperCase();
|
|
97
|
-
} catch (error) {
|
|
98
|
-
throw new Error('account not found'); // ✅ TypeScript 知道这里永不返回
|
|
99
|
-
}
|
|
100
|
-
// ✅ 不会报错
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ===== 场景8:在 catch 中返回 never 类型的函数调用 =====
|
|
104
|
-
function throwHelper(): never {
|
|
105
|
-
throw new Error('helper');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function scenario8_helperFunction(id: string | null): string {
|
|
109
|
-
try {
|
|
110
|
-
if (!id) {
|
|
111
|
-
throw new Error('id is null');
|
|
112
|
-
}
|
|
113
|
-
return id.toUpperCase();
|
|
114
|
-
} catch (error) {
|
|
115
|
-
return throwHelper(); // ⚠️ return 一个 never 类型的函数
|
|
116
|
-
}
|
|
117
|
-
// ✅ 可能不报错,但这不是正确的用法
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ===== 场景9:严格模式 - 没有 return 语句 =====
|
|
121
|
-
function scenario9_strictNoReturn(id: string | null): string {
|
|
122
|
-
try {
|
|
123
|
-
if (!id) {
|
|
124
|
-
throw new Error('id is null');
|
|
125
|
-
}
|
|
126
|
-
return id.toUpperCase();
|
|
127
|
-
} catch (error) {
|
|
128
|
-
dsl.error.throw('account.notFound');
|
|
129
|
-
}
|
|
130
|
-
// 在这里没有 return 语句
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ===== 场景10:catch 后面有额外代码块 =====
|
|
134
|
-
function scenario10_codeAfterCatch(id: string | null): string {
|
|
135
|
-
try {
|
|
136
|
-
if (!id) {
|
|
137
|
-
throw new Error('id is null');
|
|
138
|
-
}
|
|
139
|
-
return id.toUpperCase();
|
|
140
|
-
} catch (error) {
|
|
141
|
-
dsl.error.throw('account.notFound');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 这里有代码
|
|
145
|
-
console.log('after catch');
|
|
146
|
-
return 'default';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ===== 场景11:条件分支中的 throw =====
|
|
150
|
-
function scenario11_conditionalThrow(id: string | null, shouldThrow: boolean): string {
|
|
151
|
-
try {
|
|
152
|
-
if (!id) {
|
|
153
|
-
throw new Error('id is null');
|
|
154
|
-
}
|
|
155
|
-
return id.toUpperCase();
|
|
156
|
-
} catch (error) {
|
|
157
|
-
if (shouldThrow) {
|
|
158
|
-
dsl.error.throw('account.notFound');
|
|
159
|
-
}
|
|
160
|
-
return 'fallback'; // 这个 return 是必需的
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ===== 场景12:测试类型收窄 =====
|
|
165
|
-
function scenario12_typeNarrowing(value: string | number | null): string {
|
|
166
|
-
try {
|
|
167
|
-
if (value === null) {
|
|
168
|
-
throw new Error('value is null');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (typeof value === 'number') {
|
|
172
|
-
return value.toString();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return value.toUpperCase();
|
|
176
|
-
} catch (error) {
|
|
177
|
-
dsl.error.throw('validation.failed');
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
console.log('✅ 如果编译成功,说明所有场景的类型都正确');
|
|
182
|
-
|
|
183
|
-
|