1+ // modified from https://github.com/vuejs/vue-next/blob/main/packages/compiler-sfc/src/compileScript.ts
2+
13import {
24 Node ,
3- Declaration ,
4- ObjectPattern ,
55 ObjectExpression ,
6- ArrayPattern ,
7- Identifier ,
8- ExportSpecifier ,
9- Function as FunctionNode ,
106 TSType ,
117 TSTypeLiteral ,
128 TSFunctionType ,
139 ObjectProperty ,
14- ArrayExpression ,
1510 Statement ,
1611 CallExpression ,
17- RestElement ,
1812 TSInterfaceBody ,
19- AwaitExpression ,
20- VariableDeclarator ,
21- VariableDeclaration ,
2213} from '@babel/types'
2314import { types as t } from '@babel/core'
15+ import { parseExpression } from '@babel/parser'
2416import { PropTypeData } from './types'
2517
2618// Special compiler macros
@@ -32,20 +24,22 @@ const WITH_DEFAULTS = 'withDefaults'
3224export function applyMacros ( nodes : Statement [ ] ) {
3325 let hasDefinePropsCall = false
3426 let hasDefineEmitCall = false
35- const hasDefineExposeCall = false
3627 let propsRuntimeDecl : Node | undefined
3728 let propsRuntimeDefaults : Node | undefined
3829 let propsTypeDecl : TSTypeLiteral | TSInterfaceBody | undefined
3930 let propsTypeDeclRaw : Node | undefined
40- let propsIdentifier : string | undefined
4131 let emitsRuntimeDecl : Node | undefined
4232 let emitsTypeDecl :
4333 | TSFunctionType
4434 | TSTypeLiteral
4535 | TSInterfaceBody
4636 | undefined
4737 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 [ ] > = { }
4943
5044 function error (
5145 msg : string ,
@@ -196,58 +190,55 @@ export function applyMacros(nodes: Statement[]) {
196190 return false
197191 }
198192
199- /* function genRuntimeProps(props: Record<string, PropTypeData>) {
193+ function genRuntimeProps ( props : Record < string , PropTypeData > ) {
200194 const keys = Object . keys ( props )
201195 if ( ! keys . length )
202- return ''
196+ return undefined
203197
204198 // check defaults. If the default object is an object literal with only
205199 // static properties, we can directly generate more optimzied default
206200 // decalrations. Otherwise we will have to fallback to runtime merging.
207- const hasStaticDefaults
208- = propsRuntimeDefaults
201+ const hasStaticDefaults = propsRuntimeDefaults
209202 && propsRuntimeDefaults . type === 'ObjectExpression'
210203 && propsRuntimeDefaults . properties . every (
211204 node => node . type === 'ObjectProperty' && ! node . computed ,
212205 )
213206
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
232212
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
241215
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 ) )
248224
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+ }
251242
252243 nodes = nodes
253244 . map ( ( node ) => {
@@ -273,7 +264,7 @@ export function applyMacros(nodes: Statement[]) {
273264
274265 return {
275266 nodes,
276- props : propsRuntimeDecl ,
267+ props : getProps ( ) ,
277268 }
278269}
279270
@@ -290,3 +281,115 @@ function isCallOf(
290281 : test ( node . callee . name ) )
291282 )
292283}
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