8000 feat(api)!: use neverthrow for error handling (#937) · vue-macros/vue-macros@d50cab7 · GitHub
[go: up one dir, main page]

Skip to content

Commit d50cab7

Browse files
authored
feat(api)!: use neverthrow for error handling (#937)
1 parent e628d1d commit d50cab7

File tree

24 files changed

+1474
-1219
lines changed

24 files changed

+1474
-1219
lines changed

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"interface",
3434
"monoman",
3535
"mousemove",
36+
"neverthrow",
3637
"nolebase",
3738
"nuxt",
3839
"onwarn",

packages/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"dependencies": {
5353
"@vue-macros/common": "workspace:*",
54+
"neverthrow": "catalog:",
5455
"oxc-resolver": "catalog:"
5556
},
5657
"engines": {

packages/api/src/error.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { TransformError } from '@vue-macros/common'
2+
3+
export type ErrorVueSFC = '<script> is not supported, only <script setup>.'
4+
export type ErrorResolveTS =
5+
| 'Cannot resolve TS definition.'
6+
| 'Cannot resolve TS definition. Union type contains different types of results.'
7+
| `Cannot resolve TS definition: ${string}`
8+
| `Cannot resolve TS type: ${string}`
9+
export type ErrorWithDefaults =
10+
'withDefaults: first argument must be a defineProps call.'
11+
export type ErrorUnknownNode =
12+
| `Unknown node: ${string}`
13+
| `Unknown ${'import' | 'export'} type: ${string}`
14+
export type Error = TransformError<
15+
ErrorVueSFC | ErrorResolveTS | ErrorWithDefaults | ErrorUnknownNode
16+
>
17+
18+
export type ResultAsync<T> = import('neverthrow').ResultAsync<T, Error>

packages/api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from '@vue-macros/common'
22

3+
export * from './error'
34
export * from './vue'
45
export * from './ts'

packages/api/src/ts/namespace.ts

Lines changed: 128 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { TransformError } from '@vue-macros/common'
2+
import { err, ok, safeTry, type ResultAsync } from 'neverthrow'
3+
import type { ErrorUnknownNode } from '../error'
14
import { isTSDeclaration } from './is'
25
import { resolveDts } from './resolve-file'
36
import {
@@ -20,124 +23,143 @@ export function isTSNamespace(val: unknown): val is TSNamespace {
2023
*
2124
* @limitation don't support non-TS declaration (e.g. class, function...)
2225
*/
23-
export async function resolveTSNamespace(scope: TSScope): Promise<void> {
24-
if (scope.exports) return
25-
26-
const exports: TSNamespace = {
27-
[namespaceSymbol]: true,
28-
}
29-
scope.exports = exports
30-
31-
const declarations: TSNamespace = {
32-
[namespaceSymbol]: true,
33-
...scope.declarations,
34-
}
35-
scope.declarations = declarations
36-
37-
const { body, file } = resolveTSScope(scope)
38-
for (const stmt of body || []) {
39-
if (
40-
stmt.type === 'ExportDefaultDeclaration' &&
41-
isTSDeclaration(stmt.declaration)
42-
) {
43-
exports.default = await resolveTSReferencedType({
44-
scope,
45-
type: stmt.declaration,
46-
})
47-
} else if (stmt.type === 'ExportAllDeclaration') {
48-
const resolved = await resolveDts(stmt.source.value, file.filePath)
49-
if (!resolved) continue
50-
51-
const sourceScope = await getTSFile(resolved)
52-
await resolveTSNamespace(sourceScope)
53-
54-
Object.assign(exports, sourceScope.exports!)
55-
} else if (stmt.type === 'ExportNamedDeclaration') {
56-
let sourceExports: TSNamespace
57-
58-
if (stmt.source) {
26+
export function resolveTSNamespace(
27+
scope: TSScope,
28+
): ResultAsync<void, TransformError<ErrorUnknownNode>> {
29+
return safeTry(async function* () {
30+
if (scope.exports) return ok()
31+
32+
const exports: TSNamespace = {
33+
[namespaceSymbol]: true,
34+
}
35+
scope.exports = exports
36+
37+
const declarations: TSNamespace = {
38+
[namespaceSymbol]: true,
39+
...scope.declarations,
40+
}
41+
scope.declarations = declarations
42+
43+
const { body, file } = resolveTSScope(scope)
44+
for (const stmt of body || []) {
45+
if (
46+
stmt.type === 'ExportDefaultDeclaration' &&
47+
isTSDeclaration(stmt.declaration)
48+
) {
49+
exports.default = yield* resolveTSReferencedType({
50+
scope,
51+
type: stmt.declaration,
52+
})
53+
} else if (stmt.type === 'ExportAllDeclaration') {
5954
const resolved = await resolveDts(stmt.source.value, file.filePath)
6055
if (!resolved) continue
6156

62-
const scope = await getTSFile(resolved)
63-
await resolveTSNamespace(scope)
64-
sourceExports = scope.exports!
65-
} else {
66-
sourceExports = declarations
67-
}
57+
const sourceScope = await getTSFile(resolved)
58+
yield* resolveTSNamespace(sourceScope)
59+
60+
Object.assign(exports, sourceScope.exports!)
61+
} else if (stmt.type === 'ExportNamedDeclaration') {
62+
let sourceExports: TSNamespace
6863

69-
for (const specifier of stmt.specifiers) {
70-
let exported: TSNamespace[string]
71-
if (specifier.type === 'ExportDefaultSpecifier') {
72-
// export x from 'xxx'
73-
exported = sourceExports.default
74-
} else if (specifier.type === 'ExportNamespaceSpecifier') {
75-
// export * as x from 'xxx'
76-
exported = sourceExports
77-
} else if (specifier.type === 'ExportSpecifier') {
78-
// export { x } from 'xxx'
79-
exported = sourceExports![specifier.local.name]
64+
if (stmt.source) {
65+
const resolved = await resolveDts(stmt.source.value, file.filePath)
66+
if (!resolved) continue
67+
68+
const scope = await getTSFile(resolved)
69+
yield* resolveTSNamespace(scope)
70+
sourceExports = scope.exports!
8071
1241 } else {
81-
throw new Error(`Unknown export type: ${(specifier as any).type}`)
72+
sourceExports = declarations
8273
}
8374

84-
const name =
85-
specifier.exported.type === 'Identifier'
86-
? specifier.exported.name
87-
: specifier.exported.value
88-
exports[name] = exported
89-
}
75+
for (const specifier of stmt.specifiers) {
76+
let exported: TSNamespace[string]
77+
if (specifier.type === 'ExportDefaultSpecifier') {
78+
// export x from 'xxx'
79+
exported = sourceExports.default
80+
} else if (specifier.type === 'ExportNamespaceSpecifier') {
81+
// export * as x from 'xxx'
82+
exported = sourceExports
83+
} else if (specifier.type === 'ExportSpecifier') {
84+
// export { x } from 'xxx'
85+
exported = sourceExports![specifier.local.name]
86+
} else {
87+
return err(
88+
new TransformError(
89+
`Unknown export type: ${
90+
// @ts-expect-error unknown type
91+
specifier.type as string
92+
}`,
93+
),
94+
)
95+
}
9096

91-
// export interface A {}
92-
if (isTSDeclaration(stmt.declaration)) {
93-
const decl = stmt.declaration
94-
95-
if (decl.id?.type === 'Identifier') {
96-
const exportedName = decl.id.name
97-
declarations[exportedName] = exports[exportedName] =
98-
await resolveTSReferencedType({
99-
scope,
100-
type: decl,
101-
})
97+
const name =
98+
specifier.exported.type === 'Identifier'
99+
? specifier.exported.name
100+
: specifier.exported.value
101+
exports[name] = exported
102+
}
103+
104+
// export interface A {}
105+
if (isTSDeclaration(stmt.declaration)) {
106+
const decl = stmt.declaration
107+
108+
if (decl.id?.type === 'Identifier') {
109+
const exportedName = decl.id.name
110+
declarations[exportedName] = exports[exportedName] =
111+
yield* resolveTSReferencedType({
112+
scope,
113+
type: decl,
114+
})
115+
}
102116
}
103117
}
104-
}
105118

106-
// declarations
107-
else if (isTSDeclaration(stmt)) {
108-
if (stmt.id?.type !== 'Identifier') continue
109-
110-
declarations[stmt.id.name] = await resolveTSReferencedType({
111-
scope,
112-
type: stmt,
113-
})
114-
} else if (stmt.type === 'ImportDeclaration') {
115-
const resolved = await resolveDts(stmt.source.value, file.filePath)
116-
if (!resolved) continue
117-
118-
const importScope = await getTSFile(resolved)
119-
await resolveTSNamespace(importScope)
120-
const exports = importScope.exports!
121-
122-
for (const specifier of stmt.specifiers) {
123-
const local = specifier.local.name
124-
125-
let imported: TSNamespace[string]
126-
if (specifier.type === 'ImportDefaultSpecifier') {
127-
imported = exports.default
128-
} else if (specifier.type === 'ImportNamespaceSpecifier') {
129-
imported = exports
130-
} else if (specifier.type === 'ImportSpecifier') {
131-
const name =
132-
specifier.imported.type === 'Identifier'
133-
? specifier.imported.name
134-
: specifier.imported.value
135-
imported = exports[name]
136-
} else {
137-
throw new Error(`Unknown import type: ${(specifier as any).type}`)
119+
// declarations
120+
else if (isTSDeclaration(stmt)) {
121+
if (stmt.id?.type !== 'Identifier') continue
122+
123+
declarations[stmt.id.name] = yield* resolveTSReferencedType({
124+
scope,
125+
type: stmt,
126+
})
127+
} else if (stmt.type === 'ImportDeclaration') {
128+
const resolved = await resolveDts(stmt.source.value, file.filePath)
129+
if (!resolved) continue
130+
131+
const importScope = await getTSFile(resolved)
132+
yield* resolveTSNamespace(importScope)
133+
const exports = importScope.exports!
134+
135+
for (const specifier of stmt.specifiers) {
136+
const local = specifier.local.name
137+
138+
let imported: TSNamespace[string]
139+
if (specifier.type === 'ImportDefaultSpecifier') {
140+
imported = exports.default
141+
} else if (specifier.type === 'ImportNamespaceSpecifier') {
142+
imported = exports
143+
} else if (specifier.type === 'ImportSpecifier') {
144+
const name =
145+
specifier.imported.type === 'Identifier'
146+
? specifier.imported.name
147+
: specifier.imported.value
148< A18B /td>+
imported = exports[name]
149+
} else {
150+
return err(
151+
new TransformError(
152+
`Unknown import type: ${
153+
// @ts-expect-error unknown type
154+
specifier.type as string
155+
}`,
156+
),
157+
)
158+
}
159+
declarations[local] = imported
138160
}
139-
declarations[local] = imported
140161
}
141162
}
142-
}
163+
return ok()
164+
})
143165
}

0 commit comments

Comments
 (0)
0