Skip to content

Commit 6689943

Browse files
Merge pull request #91 from uniocjs/dev-groupguanfang
feat: update reflector (#89)
2 parents 0e4f713 + 22b8cf0 commit 6689943

22 files changed

+320
-55
lines changed

.changeset/happy-bobcats-rule.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/reflector": patch
3+
---
4+
5+
feat: update reflector (#89)

.changeset/three-baboons-trade.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/reflector": patch
3+
---
4+
5+
feat: update typings & add reflector to nestjs

fixtures/nestjs/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"devDependencies": {
99
"@nestjs/common": "^11.0.16",
1010
"@types/express": "^5.0.1",
11+
"@unioc/reflector": "workspace:*",
1112
"express": "^5.1.0",
1213
"rxjs": "^7.8.2",
1314
"unioc": "workspace:*"

fixtures/nestjs/src/main.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
/* eslint-disable perfectionist/sort-imports */
12
/* eslint-disable no-console */
23

4+
import '@unioc/reflector/client'
35
import { NestJS } from 'unioc/adapter-nestjs'
46
import { ExpressApp } from 'unioc/web-express'
57
import { AppModule } from './app.module'

fixtures/nestjs/tsdown.config.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Reflector from '@unioc/reflector/rolldown'
12
import { defineConfig } from 'tsdown'
23
import swc from 'unplugin-swc'
34
import { NodeRunner } from 'vite-plugin-node-runner'
@@ -11,8 +12,8 @@ export default defineConfig({
1112
clean: true,
1213
format: type === 'module' ? 'esm' : 'cjs',
1314
plugins: [
15+
Reflector(),
1416
swc.rolldown(),
15-
1617
NodeRunner({
1718
entry: './dist/main.js',
1819
// Enable this to detect the --watch flag

fixtures/reflector/main.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1-
/* eslint-disable no-console */
1+
/* eslint-disable eslint-comments/no-unlimited-disable */
2+
/* eslint-disable */
3+
import { z } from 'zod'
24
import './test'
35
import './polyfill'
46

7+
function testDecorator(..._args: any[]): any {
8+
console.log('testDecorator', _args)
9+
}
10+
11+
export const schema = z.object({
12+
a: z.string(),
13+
b: z.array(z.string()),
14+
})
15+
16+
export class testValue<T> {}
17+
518
export class Hello {
6-
constructor(_a: string, _b: Array<string>) {}
19+
constructor(_a: z.infer<typeof schema>, _b: string[]) {}
720

8-
hello: string = 'world'
21+
@testDecorator
22+
protected hello: string = 'world'
923

10-
async helloWorld(_a: string): Promise<void> {}
24+
async helloWorld(_a: z.infer<typeof schema>, _b: typeof testValue<string>): Promise<void> {}
1125
}
12-
13-
console.log(Hello[Symbol.metadata])

fixtures/reflector/polyfill.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
Reflect._uniocMetadata = (metadataKey, metadataValue) => {
2+
if (typeof Reflect.metadata === 'function')
3+
return Reflect.metadata(metadataKey, metadataValue)
4+
25
/**
36
* @param _target - The target object.
47
* @param {DecoratorContext} ctx
58
*/
6-
return (_target, ctx) => {
9+
return (_target, ctx, ...args) => {
10+
if (!ctx || typeof ctx !== 'object' || !('kind' in ctx)) {
11+
if (typeof Reflect.metadata === 'function')
12+
return Reflect.metadata(metadataKey, metadataValue)(_target, ctx, ...args)
13+
14+
throw new Error('[UNIOC] Cannot find metadata context, current environment is in legacy mode, please import `reflect-metadata` polyfill. if you want to using stage3 mode, please set `experimentalDecorators` to `true` in your tsconfig.json.')
15+
}
16+
717
if (!ctx.metadata)
818
ctx.metadata = {}
919
if (!('unioc' in ctx.metadata) || typeof ctx.metadata.unioc !== 'object')

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
"vitepress": "^1.6.3",
6565
"vitepress-plugin-comment-with-giscus": "^1.1.15",
6666
"vitepress-plugin-mermaid": "^2.0.17",
67-
"vitest": "^3.1.1"
67+
"vitest": "^3.1.1",
68+
"zod": "^3.24.3"
6869
},
6970
"simple-git-hooks": {
7071
"pre-commit": "pnpm lint-staged"

packages/core/reflector/package.json

+19
Original file line numberDiff line numberDiff line change
@@ -30,41 +30,60 @@
3030
"require": "./dist/index.cjs"
3131
},
3232
"./astro": {
33+
"types": "./dist/astro.d.ts",
3334
"import": "./dist/astro.js",
3435
"require": "./dist/astro.cjs"
3536
},
3637
"./rspack": {
38+
"types": "./dist/rspack.d.ts",
3739
"import": "./dist/rspack.js",
3840
"require": "./dist/rspack.cjs"
3941
},
4042
"./vite": {
43+
"types": "./dist/vite.d.ts",
4144
"import": "./dist/vite.js",
4245
"require": "./dist/vite.cjs"
4346
},
4447
"./webpack": {
48+
"types": "./dist/webpack.d.ts",
4549
"import": "./dist/webpack.js",
4650
"require": "./dist/webpack.cjs"
4751
},
4852
"./rollup": {
53+
"types": "./dist/rollup.d.ts",
4954
"import": "./dist/rollup.js",
5055
"require": "./dist/rollup.cjs"
5156
},
57+
"./rolldown": {
58+
"types": "./dist/rolldown.d.ts",
59+
"import": "./dist/rolldown.js",
60+
"require": "./dist/rolldown.cjs"
61+
},
5262
"./esbuild": {
63+
"types": "./dist/esbuild.d.ts",
5364
"import": "./dist/esbuild.js",
5465
"require": "./dist/esbuild.cjs"
5566
},
5667
"./nuxt": {
68+
"types": "./dist/nuxt.d.ts",
5769
"import": "./dist/nuxt.js",
5870
"require": "./dist/nuxt.cjs"
5971
},
6072
"./farm": {
73+
"types": "./dist/farm.d.ts",
6174
"import": "./dist/farm.js",
6275
"require": "./dist/farm.cjs"
6376
},
6477
"./types": {
78+
"types": "./dist/types.d.ts",
6579
"import": "./dist/types.js",
6680
"require": "./dist/types.cjs"
6781
},
82+
"./client": {
83+
"types": "./dist/client.d.ts",
84+
"import": "./dist/client.js",
85+
"require": "./dist/client.cjs"
86+
},
6887
"./*": "./*"
6988
},
7089
"main": "dist/index.cjs",

packages/core/reflector/src/client.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// eslint-disable-next-line ts/ban-ts-comment
2+
// @ts-nocheck
3+
4+
Reflect._uniocMetadata = (metadataKey, metadataValue) => {
5+
if (typeof Reflect.metadata === 'function')
6+
return Reflect.metadata(metadataKey, metadataValue)
7+
8+
/**
9+
* @param _target - The target object.
10+
* @param {DecoratorContext} ctx
11+
*/
12+
return (_target, ctx) => {
13+
if (!ctx.metadata)
14+
ctx.metadata = {}
15+
if (!('unioc' in ctx.metadata) || typeof ctx.metadata.unioc !== 'object')
16+
ctx.metadata.unioc = {}
17+
if (!('reflect' in ctx.metadata.unioc) || typeof ctx.metadata.unioc.reflect !== 'object')
18+
ctx.metadata.unioc.reflect = {}
19+
if (!('members' in ctx.metadata.unioc.reflect))
20+
ctx.metadata.unioc.reflect.members = []
21+
22+
if (ctx.kind === 'class') {
23+
ctx.metadata.unioc.reflect[metadataKey] = metadataValue
24+
}
25+
else {
26+
const foundMemberIndex = ctx.metadata.unioc.reflect.members.findIndex(member => member.name === ctx.name)
27+
28+
if (foundMemberIndex === -1) {
29+
ctx.metadata.unioc.reflect.members.push({
30+
name: ctx.name,
31+
[metadataKey]: metadataValue,
32+
})
33+
}
34+
else {
35+
ctx.metadata.unioc.reflect.members[foundMemberIndex][metadataKey] = metadataValue
36+
}
37+
}
38+
}
39+
}
40+
41+
export {}

packages/core/reflector/src/core/ast.ts

+32-41
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,16 @@
1-
import type { Class, ClassElement, MethodDefinition, ParseResult, TSType, TSTypeName } from 'oxc-parser'
1+
import type { Class, ClassElement, MethodDefinition, ParseResult } from 'oxc-parser'
22
import MagicString from 'magic-string'
3+
import { transformTSType, transformTSTypeToUniocFormat } from './typing'
34
import { walk } from './walk'
45

5-
type ConstructorType = `Object` | `Array` | `String` | `Number` | `Boolean` | `Function` | (string & {})
6-
7-
function transformTSTypeName(typeName: TSTypeName): ConstructorType {
8-
if (typeName.type === 'Identifier') {
9-
return typeName.typeAnnotation ? transformTSType(typeName.typeAnnotation) : `(() => { try { return ${typeName.name} } catch (e) { return Object } })()`
10-
}
11-
return transformTSTypeName(typeName.left)
12-
}
13-
14-
function transformTSType(type: TSType): ConstructorType {
15-
switch (type.type) {
16-
case 'TSStringKeyword':
17-
return 'String'
18-
case 'TSNumberKeyword':
19-
return 'Number'
20-
case 'TSBooleanKeyword':
21-
return 'Boolean'
22-
case 'TSFunctionType':
23-
return 'Function'
24-
case 'TSArrayType':
25-
return 'Array'
26-
case 'TSTypeReference':
27-
return transformTSTypeName(type.typeName)
28-
default:
29-
return 'Object'
30-
}
31-
}
32-
336
function transformClassMembers(members: ClassElement[], s: MagicString): void {
347
for (const member of members) {
35-
if (member.type === 'StaticBlock')
36-
continue
37-
if (member.type === 'TSAbstractMethodDefinition')
38-
continue
39-
if (member.type === 'TSIndexSignature')
40-
continue
41-
if (member.type === 'TSAbstractPropertyDefinition')
42-
continue
43-
if (member.type === 'TSAbstractAccessorProperty')
8+
if (member.type === 'StaticBlock' || member.type === 'TSAbstractMethodDefinition' || member.type === 'TSIndexSignature' || member.type === 'TSAbstractPropertyDefinition' || member.type === 'TSAbstractAccessorProperty')
449
continue
4510

4611
if (member.type === 'PropertyDefinition') {
4712
const memberDecoratorStart = member.decorators?.[0]?.start || member.start
48-
s.appendLeft(memberDecoratorStart, `@Reflect._uniocMetadata("design:type", ${member.typeAnnotation?.typeAnnotation ? transformTSType(member.typeAnnotation.typeAnnotation) : 'Object'})\n`)
13+
s.appendLeft(memberDecoratorStart, `@Reflect._uniocMetadata("design:type", ${member.typeAnnotation?.typeAnnotation ? transformTSType(member.typeAnnotation.typeAnnotation) : 'Object'})\n${s.getIndentString()}`)
4914
}
5015
else if (member.type === 'MethodDefinition') {
5116
if (member.kind === 'constructor')
@@ -54,7 +19,12 @@ function transformClassMembers(members: ClassElement[], s: MagicString): void {
5419
const memberDecoratorStart = member.decorators?.[0]?.start || member.start
5520
const returnType = member.value.returnType?.typeAnnotation ? transformTSType(member.value.returnType.typeAnnotation) : 'Object'
5621
const paramTypes = member.value.params.map(param => param.typeAnnotation?.typeAnnotation ? transformTSType(param.typeAnnotation.typeAnnotation) : 'Object').join(',')
57-
s.appendLeft(memberDecoratorStart, `@Reflect._uniocMetadata("design:type", Function)\n@Reflect._uniocMetadata("design:returntype", ${returnType})\n@Reflect._uniocMetadata("design:paramtypes", [${paramTypes}])\n`)
22+
const paramTypesInfo = member.value.params.map(param => param.typeAnnotation?.typeAnnotation ? transformTSTypeToUniocFormat(param.typeAnnotation.typeAnnotation) : 'Object').join(',')
23+
24+
s.appendLeft(memberDecoratorStart, `@Reflect._uniocMetadata("design:type", Function)\n`)
25+
.appendLeft(memberDecoratorStart, `${s.getIndentString()}@Reflect._uniocMetadata("design:returntype", ${returnType})\n`)
26+
.appendLeft(memberDecoratorStart, `${s.getIndentString()}@Reflect._uniocMetadata("design:paramtypes", [${paramTypes}])\n`)
27+
.appendLeft(memberDecoratorStart, `${s.getIndentString()}@Reflect._uniocMetadata("design:paramtypesInfo", [${paramTypesInfo}])\n${s.getIndentString()}`)
5828
}
5929
}
6030
}
@@ -67,7 +37,25 @@ function transformClassDeclaration(node: Class, s: MagicString): void {
6737
.filter(Boolean)
6838
.map(value => transformTSType(value!)) || []
6939

70-
s.appendLeft(decoratorStart, `@Reflect._uniocMetadata("design:type", Function)\n@Reflect._uniocMetadata("design:paramtypes", [${paramTypes.join(',')}])\n`)
40+
s.appendLeft(decoratorStart, `\n@Reflect._uniocMetadata("design:type", Function)\n`)
41+
.appendLeft(decoratorStart, `@Reflect._uniocMetadata("design:paramtypes", [${paramTypes.join(',')}])\n`)
42+
}
43+
44+
function checkHasDecorator(node: Class): boolean {
45+
let hasDecorator = false
46+
if (node.decorators && node.decorators.length > 0)
47+
hasDecorator = true
48+
49+
for (const body of node.body.body) {
50+
if (body.type === 'MethodDefinition' && body.decorators && body.decorators.length > 0)
51+
return true
52+
if (body.type === 'PropertyDefinition' && body.decorators && body.decorators.length > 0)
53+
return true
54+
if (body.type === 'AccessorProperty' && body.decorators && body.decorators.length > 0)
55+
return true
56+
}
57+
58+
return hasDecorator
7159
}
7260

7361
export async function transform(ast: ParseResult, fullText: string): Promise<MagicString> {
@@ -78,6 +66,9 @@ export async function transform(ast: ParseResult, fullText: string): Promise<Mag
7866
if (node.type !== 'ClassDeclaration')
7967
return
8068

69+
if (!checkHasDecorator(node))
70+
return
71+
8172
transformClassDeclaration(node, s)
8273
transformClassMembers(node.body.body, s)
8374
},
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { TSType, TSTypeName } from 'oxc-parser'
2+
3+
type ConstructorType = `Object` | `Array` | `String` | `Number` | `Boolean` | `Function` | (string & {})
4+
5+
function createErrorHandler(name: string): string {
6+
return `(() => { try { return ${name} } catch (e) { return Object } })()`
7+
}
8+
9+
function transformTSTypeName(typeName: TSTypeName): ConstructorType {
10+
if (typeName.type === 'Identifier') {
11+
return typeName.typeAnnotation ? transformTSType(typeName.typeAnnotation) : createErrorHandler(typeName.name)
12+
}
13+
return transformTSTypeName(typeName.left)
14+
}
15+
16+
export function transformTSType(type: TSType): ConstructorType {
17+
switch (type.type) {
18+
case 'TSStringKeyword':
19+
return 'String'
20+
case 'TSNumberKeyword':
21+
return 'Number'
22+
case 'TSBooleanKeyword':
23+
return 'Boolean'
24+
case 'TSFunctionType':
25+
return 'Function'
26+
case 'TSArrayType':
27+
return 'Array'
28+
case 'TSTypeReference':
29+
return transformTSTypeName(type.typeName)
30+
case 'TSTypeQuery':
31+
switch (type.exprName.type) {
32+
case 'Identifier':
33+
return createErrorHandler(type.exprName.name)
34+
case 'TSQualifiedName':
35+
return transformTSTypeName(type.exprName.left)
36+
default:
37+
return 'Object'
38+
}
39+
default:
40+
return 'Object'
41+
}
42+
}
43+
44+
export function transformTSTypeToUniocFormat(type: TSType): string {
45+
switch (type.type) {
46+
case 'TSTypeReference': {
47+
const typeParameters = type.typeArguments?.params.map(param => transformTSTypeToUniocFormat(param)) || []
48+
return `{ type: ${transformTSType(type)}, typeParameters: [${typeParameters?.join(',')}] }`
49+
}
50+
case 'TSTypeQuery': {
51+
const typeParameters = type.typeArguments?.params.map(param => transformTSTypeToUniocFormat(param)) || []
52+
return `{ type: ${transformTSType(type)}, typeParameters: [${typeParameters?.join(',')}] }`
53+
}
54+
case 'TSImportType': {
55+
const typeParameters = type.typeArguments?.params.map(param => transformTSTypeToUniocFormat(param)) || []
56+
return `{ type: ${transformTSType(type)}, typeParameters: [${typeParameters?.join(',')}] }`
57+
}
58+
default:
59+
return `{ type: ${transformTSType(type)}, typeParameters: [] }`
60+
}
61+
}
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import type { UnpluginInstance } from 'unplugin'
12
import type { Options } from './types'
23
import { createEsbuildPlugin } from 'unplugin'
34
import { unpluginFactory } from '.'
45

5-
export default createEsbuildPlugin(unpluginFactory) as ReturnType<typeof createEsbuildPlugin<Options | undefined>>
6+
export default createEsbuildPlugin(unpluginFactory) as UnpluginInstance<Options>['esbuild']

packages/core/reflector/src/farm.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import type { UnpluginInstance } from 'unplugin'
12
import type { Options } from './types'
23
import { createFarmPlugin } from 'unplugin'
34
import { unpluginFactory } from '.'
45

5-
export default createFarmPlugin(unpluginFactory) as ReturnType<typeof createFarmPlugin<Options | undefined>>
6+
export default createFarmPlugin(unpluginFactory) as UnpluginInstance<Options>['farm']

0 commit comments

Comments
 (0)