]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): avoid all hard errors when inferring runtime type
authorEvan You <yyx990803@gmail.com>
Fri, 21 Apr 2023 08:48:21 +0000 (16:48 +0800)
committerEvan You <yyx990803@gmail.com>
Fri, 21 Apr 2023 08:48:21 +0000 (16:48 +0800)
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
packages/compiler-sfc/src/script/resolveType.ts

index c7e2b9b694a984b1491a7d99f5739f417bf5117d..dd8f82e1db013372985c08d1f6e9c76d30020270 100644 (file)
@@ -690,6 +690,12 @@ describe('resolveType', () => {
     test('should not error on unresolved type when inferring runtime type', () => {
       expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow()
       expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow()
+      expect(() =>
+        resolve(`
+        import type P from 'unknown'
+        defineProps<{ foo: P }>()
+      `)
+      ).not.toThrow()
     })
   })
 })
index 526569843ecad706257c55c63be9a85b475a6c4a..13fe57e46a54c5161c6a658529c8ce974eacec51 100644 (file)
@@ -1180,156 +1180,164 @@ export function inferRuntimeType(
   node: Node & MaybeWithScope,
   scope = node._ownerScope || ctxToScope(ctx)
 ): string[] {
-  switch (node.type) {
-    case 'TSStringKeyword':
-      return ['String']
-    case 'TSNumberKeyword':
-      return ['Number']
-    case 'TSBooleanKeyword':
-      return ['Boolean']
-    case 'TSObjectKeyword':
-      return ['Object']
-    case 'TSNullKeyword':
-      return ['null']
-    case 'TSTypeLiteral':
-    case 'TSInterfaceDeclaration': {
-      // TODO (nice to have) generate runtime property validation
-      const types = new Set<string>()
-      const members =
-        node.type === 'TSTypeLiteral' ? node.members : node.body.body
-      for (const m of members) {
-        if (
-          m.type === 'TSCallSignatureDeclaration' ||
-          m.type === 'TSConstructSignatureDeclaration'
-        ) {
-          types.add('Function')
-        } else {
-          types.add('Object')
+  try {
+    switch (node.type) {
+      case 'TSStringKeyword':
+        return ['String']
+      case 'TSNumberKeyword':
+        return ['Number']
+      case 'TSBooleanKeyword':
+        return ['Boolean']
+      case 'TSObjectKeyword':
+        return ['Object']
+      case 'TSNullKeyword':
+        return ['null']
+      case 'TSTypeLiteral':
+      case 'TSInterfaceDeclaration': {
+        // TODO (nice to have) generate runtime property validation
+        const types = new Set<string>()
+        const members =
+          node.type === 'TSTypeLiteral' ? node.members : node.body.body
+        for (const m of members) {
+          if (
+            m.type === 'TSCallSignatureDeclaration' ||
+            m.type === 'TSConstructSignatureDeclaration'
+          ) {
+            types.add('Function')
+          } else {
+            types.add('Object')
+          }
         }
+        return types.size ? Array.from(types) : ['Object']
       }
-      return types.size ? Array.from(types) : ['Object']
-    }
-    case 'TSPropertySignature':
-      if (node.typeAnnotation) {
-        return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope)
-      }
-    case 'TSMethodSignature':
-    case 'TSFunctionType':
-      return ['Function']
-    case 'TSArrayType':
-    case 'TSTupleType':
-      // TODO (nice to have) generate runtime element type/length checks
-      return ['Array']
-
-    case 'TSLiteralType':
-      switch (node.literal.type) {
-        case 'StringLiteral':
-          return ['String']
-        case 'BooleanLiteral':
-          return ['Boolean']
-        case 'NumericLiteral':
-        case 'BigIntLiteral':
-          return ['Number']
-        default:
-          return [UNKNOWN_TYPE]
-      }
-
-    case 'TSTypeReference': {
-      const resolved = resolveTypeReference(ctx, node, scope)
-      if (resolved) {
-        return inferRuntimeType(ctx, resolved, resolved._ownerScope)
-      }
-      if (node.typeName.type === 'Identifier') {
-        switch (node.typeName.name) {
-          case 'Array':
-          case 'Function':
-          case 'Object':
-          case 'Set':
-          case 'Map':
-          case 'WeakSet':
-          case 'WeakMap':
-          case 'Date':
-          case 'Promise':
-            return [node.typeName.name]
-
-          // TS built-in utility types
-          // https://www.typescriptlang.org/docs/handbook/utility-types.html
-          case 'Partial':
-          case 'Required':
-          case 'Readonly':
-          case 'Record':
-          case 'Pick':
-          case 'Omit':
-          case 'InstanceType':
-            return ['Object']
-
-          case 'Uppercase':
-          case 'Lowercase':
-          case 'Capitalize':
-          case 'Uncapitalize':
+      case 'TSPropertySignature':
+        if (node.typeAnnotation) {
+          return inferRuntimeType(
+            ctx,
+            node.typeAnnotation.typeAnnotation,
+            scope
+          )
+        }
+      case 'TSMethodSignature':
+      case 'TSFunctionType':
+        return ['Function']
+      case 'TSArrayType':
+      case 'TSTupleType':
+        // TODO (nice to have) generate runtime element type/length checks
+        return ['Array']
+
+      case 'TSLiteralType':
+        switch (node.literal.type) {
+          case 'StringLiteral':
             return ['String']
+          case 'BooleanLiteral':
+            return ['Boolean']
+          case 'NumericLiteral':
+          case 'BigIntLiteral':
+            return ['Number']
+          default:
+            return [UNKNOWN_TYPE]
+        }
 
-          case 'Parameters':
-          case 'ConstructorParameters':
-            return ['Array']
-
-          case 'NonNullable':
-            if (node.typeParameters && node.typeParameters.params[0]) {
-              return inferRuntimeType(
-                ctx,
-                node.typeParameters.params[0],
-                scope
-              ).filter(t => t !== 'null')
-            }
-            break
-          case 'Extract':
-            if (node.typeParameters && node.typeParameters.params[1]) {
-              return inferRuntimeType(ctx, node.typeParameters.params[1], scope)
-            }
-            break
-          case 'Exclude':
-          case 'OmitThisParameter':
-            if (node.typeParameters && node.typeParameters.params[0]) {
-              return inferRuntimeType(ctx, node.typeParameters.params[0], scope)
-            }
-            break
+      case 'TSTypeReference': {
+        const resolved = resolveTypeReference(ctx, node, scope)
+        if (resolved) {
+          return inferRuntimeType(ctx, resolved, resolved._ownerScope)
+        }
+        if (node.typeName.type === 'Identifier') {
+          switch (node.typeName.name) {
+            case 'Array':
+            case 'Function':
+            case 'Object':
+            case 'Set':
+            case 'Map':
+            case 'WeakSet':
+            case 'WeakMap':
+            case 'Date':
+            case 'Promise':
+              return [node.typeName.name]
+
+            // TS built-in utility types
+            // https://www.typescriptlang.org/docs/handbook/utility-types.html
+            case 'Partial':
+            case 'Required':
+            case 'Readonly':
+            case 'Record':
+            case 'Pick':
+            case 'Omit':
+            case 'InstanceType':
+              return ['Object']
+
+            case 'Uppercase':
+            case 'Lowercase':
+            case 'Capitalize':
+            case 'Uncapitalize':
+              return ['String']
+
+            case 'Parameters':
+            case 'ConstructorParameters':
+              return ['Array']
+
+            case 'NonNullable':
+              if (node.typeParameters && node.typeParameters.params[0]) {
+                return inferRuntimeType(
+                  ctx,
+                  node.typeParameters.params[0],
+                  scope
+                ).filter(t => t !== 'null')
+              }
+              break
+            case 'Extract':
+              if (node.typeParameters && node.typeParameters.params[1]) {
+                return inferRuntimeType(
+                  ctx,
+                  node.typeParameters.params[1],
+                  scope
+                )
+              }
+              break
+            case 'Exclude':
+            case 'OmitThisParameter':
+              if (node.typeParameters && node.typeParameters.params[0]) {
+                return inferRuntimeType(
+                  ctx,
+                  node.typeParameters.params[0],
+                  scope
+                )
+              }
+              break
+          }
         }
+        // cannot infer, fallback to UNKNOWN: ThisParameterType
+        break
       }
-      // cannot infer, fallback to UNKNOWN: ThisParameterType
-      break
-    }
 
-    case 'TSParenthesizedType':
-      return inferRuntimeType(ctx, node.typeAnnotation, scope)
+      case 'TSParenthesizedType':
+        return inferRuntimeType(ctx, node.typeAnnotation, scope)
 
-    case 'TSUnionType':
-      return flattenTypes(ctx, node.types, scope)
-    case 'TSIntersectionType': {
-      return flattenTypes(ctx, node.types, scope).filter(
-        t => t !== UNKNOWN_TYPE
-      )
-    }
+      case 'TSUnionType':
+        return flattenTypes(ctx, node.types, scope)
+      case 'TSIntersectionType': {
+        return flattenTypes(ctx, node.types, scope).filter(
+          t => t !== UNKNOWN_TYPE
+        )
+      }
 
-    case 'TSEnumDeclaration':
-      return inferEnumType(node)
+      case 'TSEnumDeclaration':
+        return inferEnumType(node)
 
-    case 'TSSymbolKeyword':
-      return ['Symbol']
+      case 'TSSymbolKeyword':
+        return ['Symbol']
 
-    case 'TSIndexedAccessType': {
-      try {
+      case 'TSIndexedAccessType': {
         const types = resolveIndexType(ctx, node, scope)
         return flattenTypes(ctx, types, scope)
-      } catch (e) {
-        break
       }
-    }
 
-    case 'ClassDeclaration':
-      return ['Object']
+      case 'ClassDeclaration':
+        return ['Object']
 
-    case 'TSImportType': {
-      try {
+      case 'TSImportType': {
         const sourceScope = importSourceToScope(
           ctx,
           node.argument,
@@ -1340,21 +1348,23 @@ export function inferRuntimeType(
         if (resolved) {
           return inferRuntimeType(ctx, resolved, resolved._ownerScope)
         }
-      } catch (e) {}
-      break
-    }
+        break
+      }
 
-    case 'TSTypeQuery': {
-      const id = node.exprName
-      if (id.type === 'Identifier') {
-        // typeof only support identifier in local scope
-        const matched = scope.declares[id.name]
-        if (matched) {
-          return inferRuntimeType(ctx, matched, matched._ownerScope)
+      case 'TSTypeQuery': {
+        const id = node.exprName
+        if (id.type === 'Identifier') {
+          // typeof only support identifier in local scope
+          const matched = scope.declares[id.name]
+          if (matched) {
+            return inferRuntimeType(ctx, matched, matched._ownerScope)
+          }
         }
+        break
       }
-      break
     }
+  } catch (e) {
+    // always soft fail on failed runtime type inference
   }
   return [UNKNOWN_TYPE] // no runtime check
 }