1
+ // modified from https://github.com/vuejs/vue-next/blob/main/packages/compiler-sfc/src/compileScript.ts
2
+
1
3
import {
2
4
Node ,
3
- Declaration ,
4
- ObjectPattern ,
5
5
ObjectExpression ,
6
- ArrayPattern ,
7
- Identifier ,
8
- ExportSpecifier ,
9
- Function as FunctionNode ,
10
6
TSType ,
11
7
TSTypeLiteral ,
12
8
TSFunctionType ,
13
9
ObjectProperty ,
14
- ArrayExpression ,
15
10
Statement ,
16
11
CallExpression ,
17
- RestElement ,
18
12
TSInterfaceBody ,
19
- AwaitExpression ,
20
- VariableDeclarator ,
21
- VariableDeclaration ,
22
13
} from '@babel/types'
23
14
import { types as t } from '@babel/core'
15
+ import { parseExpression } from '@babel/parser'
24
16
import { PropTypeData } from './types'
25
17
26
18
// Special compiler macros
@@ -32,20 +24,22 @@ const WITH_DEFAULTS = 'withDefaults'
32
24
export function applyMacros ( nodes : Statement [ ] ) {
33
25
let hasDefinePropsCall = false
34
26
let hasDefineEmitCall = false
35
- const hasDefineExposeCall = false
36
27
let propsRuntimeDecl : Node | undefined
37
28
let propsRuntimeDefaults : Node | undefined
38
29
let propsTypeDecl : TSTypeLiteral | TSInterfaceBody | undefined
39
30
let propsTypeDeclRaw : Node | undefined
40
- let propsIdentifier : string | undefined
41
31
let emitsRuntimeDecl : Node | undefined
42
32
let emitsTypeDecl :
43
33
| TSFunctionType
44
34
| TSTypeLiteral
45
35
| TSInterfaceBody
46
36
| undefined
47
37
let emitsTypeDeclRaw : Node | undefined
48
- let emitIdentifier : string | undefined
38
+
39
+ // props/emits declared via types
40
+ const typeDeclaredProps : Record < string , PropTypeData > = { }
41
+ // record declared types for runtime props type generation
42
+ const declaredTypes : Record < string , string [ ] > = { }
49
43
50
44
function error (
51
45
msg : string ,
@@ -196,58 +190,55 @@ export function applyMacros(nodes: Statement[]) {
196
190
return false
197
191
}
198
192
199
- /* function genRuntimeProps(props: Record<string, PropTypeData>) {
193
+ function genRuntimeProps ( props : Record < string , PropTypeData > ) {
200
194
const keys = Object . keys ( props )
201
195
if ( ! keys . length )
202
- return ''
196
+ return undefined
203
197
204
198
// check defaults. If the default object is an object literal with only
205
199
// static properties, we can directly generate more optimzied default
206
200
// decalrations. Otherwise we will have to fallback to runtime merging.
207
- const hasStaticDefaults
208
- = propsRuntimeDefaults
201
+ const hasStaticDefaults = propsRuntimeDefaults
209
202
&& propsRuntimeDefaults . type === 'ObjectExpression'
210
203
&& propsRuntimeDefaults . properties . every (
211
204
node => node . type === 'ObjectProperty' && ! node . computed ,
212
205
)
213
206
214
- let propsDecls = `{
215
- ${keys
216
- .map((key) => {
217
- let defaultString: string | undefined
218
- if (hasStaticDefaults) {
219
- const prop = (
220
- propsRuntimeDefaults as ObjectExpression
221
- ).properties.find(
222
- (node: any) => node.key.name === key,
223
- ) as ObjectProperty
224
- if (prop) {
225
- // prop has corresponding static default value
226
- defaultString = `default: ${source.slice(
227
- prop.value.start! + startOffset,
228
- prop.value.end! + startOffset,
229
- )}`
230
- }
231
- }
207
+ return t . objectExpression (
208
+ Object . entries ( props ) . map ( ( [ key , value ] ) => {
209
+ const prop = hasStaticDefaults
210
+ ? ( propsRuntimeDefaults as ObjectExpression ) . properties . find ( ( node : any ) => node . key . name === key ) as ObjectProperty
211
+ : undefined
232
212
233
- const { type, required } = props[key]
234
- return `${key}: { type: ${toRuntimeTypeString(
235
- type,
236
- )}, required: ${required}${
237
- defaultString ? `, ${defaultString}` : ''
238
- } }`
239
- })
240
- .join(',\n ')}\n }`
213
+ if ( prop )
214
+ value . required = false
241
215
242
- if (propsRuntimeDefaults && !hasStaticDefaults) {
243
- propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(
244
- propsRuntimeDefaults.start! + startOffset,
245
- propsRuntimeDefaults.end! + startOffset,
246
- )})`
247
- }
216
+ const entries = Object . entries ( value ) . map ( ( [ key , value ] ) =>
217
+ key === 'type'
218
+ ? t . objectProperty ( t . identifier ( key ) , t . arrayExpression ( value . map ( ( i : any ) => t . identifier ( i ) ) ) as any )
219
+ : t . objectProperty ( t . identifier ( key ) , parseExpression ( JSON . stringify ( value ) ) as any ) ,
220
+ )
221
+
222
+ if ( prop )
223
+ entries . push ( t . objectProperty ( t . identifier ( 'default' ) , prop . value as any ) )
248
224
249
- return `\n props: ${propsDecls} as unknown as undefined,`
250
- } */
225
+ return t . objectProperty (
226
+ t . identifier ( key ) ,
227
+ t . objectExpression ( entries ) ,
228
+ )
229
+ } ) ,
230
+ )
231
+ }
232
+
233
+ function getProps ( ) {
234
+ if ( propsRuntimeDecl )
235
+ return propsRuntimeDecl
236
+
237
+ if ( propsTypeDecl ) {
238
+ extractRuntimeProps ( propsTypeDecl , typeDeclaredProps , declaredTypes )
239
+ return genRuntimeProps ( typeDeclaredProps )
240
+ }
241
+ }
251
242
252
243
nodes = nodes
253
244
. map ( ( node ) => {
@@ -273,7 +264,7 @@ export function applyMacros(nodes: Statement[]) {
273
264
274
265
return {
275
266
nodes,
276
- props : propsRuntimeDecl ,
267
+ props : getProps ( ) ,
277
268
}
278
269
}
279
270
@@ -290,3 +281,115 @@ function isCallOf(
290
281
: test ( node . callee . name ) )
291
282
)
292
283
}
284
+
285
+ function extractRuntimeProps (
286
+ node : TSTypeLiteral | TSInterfaceBody ,
287
+ props : Record < string , PropTypeData > ,
288
+ declaredTypes : Record < string , string [ ] > ,
289
+ ) {
290
+ const members = node . type === 'TSTypeLiteral' ? node . members : node . body
291
+ for ( const m of members ) {
292
+ if (
293
+ ( m . type === 'TSPropertySignature' || m . type === 'TSMethodSignature' )
294
+ && m . key . type === 'Identifier'
295
+ ) {
296
+ let type
297
+ if ( m . type === 'TSMethodSignature' ) {
298
+ type = [ 'Function' ]
299
+ }
300
+ else if ( m . typeAnnotation ) {
301
+ type = inferRuntimeType (
302
+ m . typeAnnotation . typeAnnotation ,
303
+ declaredTypes ,
304
+ )
305
+ }
306
+ props [ m . key . name ] = {
307
+ key : m . key . name ,
308
+ required : ! m . optional ,
309
+ type : type || [ 'null' ] ,
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ function inferRuntimeType (
316
+ node : TSType ,
317
+ declaredTypes : Record < string , string [ ] > ,
318
+ ) : string [ ] {
319
+ switch ( node . type ) {
320
+ case 'TSStringKeyword' :
321
+ return [ 'String' ]
322
+ case 'TSNumberKeyword' :
323
+ return [ 'Number' ]
324
+ case 'TSBooleanKeyword' :
325
+ return [ 'Boolean' ]
326
+ case 'TSObjectKeyword' :
327
+ return [ 'Object' ]
328
+ case 'TSTypeLiteral' :
329
+ // TODO (nice to have) generate runtime property validation
330
+ return [ 'Object' ]
331
+ case 'TSFunctionType' :
332
+ return [ 'Function' ]
333
+ case 'TSArrayType' :
334
+ case 'TSTupleType' :
335
+ // TODO (nice to have) generate runtime element type/length checks
336
+ return [ 'Array' ]
337
+
338
+ case 'TSLiteralType' :
339
+ switch ( node . literal . type ) {
340
+ case 'StringLiteral' :
341
+ return [ 'String' ]
342
+ case 'BooleanLiteral' :
343
+ return [ 'Boolean' ]
344
+ case 'NumericLiteral' :
345
+ case 'BigIntLiteral' :
346
+ return [ 'Number' ]
347
+ default :
348
+ return [ 'null' ]
349
+ }
350
+
351
+ case 'TSTypeReference' :
352
+ if ( node . typeName . type === 'Identifier' ) {
353
+ if ( declaredTypes [ node . typeName . name ] )
354
+ return declaredTypes [ node . typeName . name ]
355
+
356
+ switch ( node . typeName . name ) {
357
+ case 'Array' :
358
+ case 'Function' :
359
+ case 'Object' :
360
+ case 'Set' :
361
+ case 'Map' :
362
+ case 'WeakSet' :
363
+ case 'WeakMap' :
364
+ return [ node . typeName . name ]
365
+ case 'Record' :
366
+ case 'Partial' :
367
+ case 'Readonly' :
368
+ case 'Pick' :
369
+ case 'Omit' :
370
+ case 'Exclude' :
371
+ case 'Extract' :
372
+ case 'Required' :
373
+ case 'InstanceType' :
374
+ return [ 'Object' ]
375
+ }
376
+ }
377
+ return [ 'null' ]
378
+
379
+ case 'TSParenthesizedType' :
380
+ return inferRuntimeType ( node . typeAnnotation , declaredTypes )
381
+ case 'TSUnionType' :
382
+ return [
383
+ ...new Set (
384
+ [ ] . concat (
385
+ ...( node . types . map ( t => inferRuntimeType ( t , declaredTypes ) ) as any ) ,
386
+ ) ,
387
+ ) ,
388
+ ]
389
+ case 'TSIntersectionType' :
390
+ return [ 'Object' ]
391
+
392
+ default :
393
+ return [ 'null' ] // no runtime check
394
+ }
395
+ }
0 commit comments