mirror of https://github.com/vuejs/core.git
fix(compiler-sfc): avoid all hard errors when inferring runtime type
This commit is contained in:
parent
1447596bf4
commit
2d9f6f9264
|
@ -690,6 +690,12 @@ describe('resolveType', () => {
|
||||||
test('should not error on unresolved type when inferring runtime type', () => {
|
test('should not error on unresolved type when inferring runtime type', () => {
|
||||||
expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow()
|
expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow()
|
||||||
expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow()
|
expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow()
|
||||||
|
expect(() =>
|
||||||
|
resolve(`
|
||||||
|
import type P from 'unknown'
|
||||||
|
defineProps<{ foo: P }>()
|
||||||
|
`)
|
||||||
|
).not.toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1180,156 +1180,164 @@ export function inferRuntimeType(
|
||||||
node: Node & MaybeWithScope,
|
node: Node & MaybeWithScope,
|
||||||
scope = node._ownerScope || ctxToScope(ctx)
|
scope = node._ownerScope || ctxToScope(ctx)
|
||||||
): string[] {
|
): string[] {
|
||||||
switch (node.type) {
|
try {
|
||||||
case 'TSStringKeyword':
|
switch (node.type) {
|
||||||
return ['String']
|
case 'TSStringKeyword':
|
||||||
case 'TSNumberKeyword':
|
return ['String']
|
||||||
return ['Number']
|
case 'TSNumberKeyword':
|
||||||
case 'TSBooleanKeyword':
|
return ['Number']
|
||||||
return ['Boolean']
|
case 'TSBooleanKeyword':
|
||||||
case 'TSObjectKeyword':
|
return ['Boolean']
|
||||||
return ['Object']
|
case 'TSObjectKeyword':
|
||||||
case 'TSNullKeyword':
|
return ['Object']
|
||||||
return ['null']
|
case 'TSNullKeyword':
|
||||||
case 'TSTypeLiteral':
|
return ['null']
|
||||||
case 'TSInterfaceDeclaration': {
|
case 'TSTypeLiteral':
|
||||||
// TODO (nice to have) generate runtime property validation
|
case 'TSInterfaceDeclaration': {
|
||||||
const types = new Set<string>()
|
// TODO (nice to have) generate runtime property validation
|
||||||
const members =
|
const types = new Set<string>()
|
||||||
node.type === 'TSTypeLiteral' ? node.members : node.body.body
|
const members =
|
||||||
for (const m of members) {
|
node.type === 'TSTypeLiteral' ? node.members : node.body.body
|
||||||
if (
|
for (const m of members) {
|
||||||
m.type === 'TSCallSignatureDeclaration' ||
|
if (
|
||||||
m.type === 'TSConstructSignatureDeclaration'
|
m.type === 'TSCallSignatureDeclaration' ||
|
||||||
) {
|
m.type === 'TSConstructSignatureDeclaration'
|
||||||
types.add('Function')
|
) {
|
||||||
} else {
|
types.add('Function')
|
||||||
types.add('Object')
|
} else {
|
||||||
|
types.add('Object')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return types.size ? Array.from(types) : ['Object']
|
||||||
}
|
}
|
||||||
return types.size ? Array.from(types) : ['Object']
|
case 'TSPropertySignature':
|
||||||
}
|
if (node.typeAnnotation) {
|
||||||
case 'TSPropertySignature':
|
return inferRuntimeType(
|
||||||
if (node.typeAnnotation) {
|
ctx,
|
||||||
return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope)
|
node.typeAnnotation.typeAnnotation,
|
||||||
}
|
scope
|
||||||
case 'TSMethodSignature':
|
)
|
||||||
case 'TSFunctionType':
|
}
|
||||||
return ['Function']
|
case 'TSMethodSignature':
|
||||||
case 'TSArrayType':
|
case 'TSFunctionType':
|
||||||
case 'TSTupleType':
|
return ['Function']
|
||||||
// TODO (nice to have) generate runtime element type/length checks
|
case 'TSArrayType':
|
||||||
return ['Array']
|
case 'TSTupleType':
|
||||||
|
// TODO (nice to have) generate runtime element type/length checks
|
||||||
|
return ['Array']
|
||||||
|
|
||||||
case 'TSLiteralType':
|
case 'TSLiteralType':
|
||||||
switch (node.literal.type) {
|
switch (node.literal.type) {
|
||||||
case 'StringLiteral':
|
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':
|
|
||||||
return ['String']
|
return ['String']
|
||||||
|
case 'BooleanLiteral':
|
||||||
case 'Parameters':
|
return ['Boolean']
|
||||||
case 'ConstructorParameters':
|
case 'NumericLiteral':
|
||||||
return ['Array']
|
case 'BigIntLiteral':
|
||||||
|
return ['Number']
|
||||||
case 'NonNullable':
|
default:
|
||||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
return [UNKNOWN_TYPE]
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'TSParenthesizedType':
|
case 'TSTypeReference': {
|
||||||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
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]
|
||||||
|
|
||||||
case 'TSUnionType':
|
// TS built-in utility types
|
||||||
return flattenTypes(ctx, node.types, scope)
|
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||||
case 'TSIntersectionType': {
|
case 'Partial':
|
||||||
return flattenTypes(ctx, node.types, scope).filter(
|
case 'Required':
|
||||||
t => t !== UNKNOWN_TYPE
|
case 'Readonly':
|
||||||
)
|
case 'Record':
|
||||||
}
|
case 'Pick':
|
||||||
|
case 'Omit':
|
||||||
|
case 'InstanceType':
|
||||||
|
return ['Object']
|
||||||
|
|
||||||
case 'TSEnumDeclaration':
|
case 'Uppercase':
|
||||||
return inferEnumType(node)
|
case 'Lowercase':
|
||||||
|
case 'Capitalize':
|
||||||
|
case 'Uncapitalize':
|
||||||
|
return ['String']
|
||||||
|
|
||||||
case 'TSSymbolKeyword':
|
case 'Parameters':
|
||||||
return ['Symbol']
|
case 'ConstructorParameters':
|
||||||
|
return ['Array']
|
||||||
|
|
||||||
case 'TSIndexedAccessType': {
|
case 'NonNullable':
|
||||||
try {
|
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||||
const types = resolveIndexType(ctx, node, scope)
|
return inferRuntimeType(
|
||||||
return flattenTypes(ctx, types, scope)
|
ctx,
|
||||||
} catch (e) {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
case 'ClassDeclaration':
|
case 'TSParenthesizedType':
|
||||||
return ['Object']
|
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||||
|
|
||||||
case 'TSImportType': {
|
case 'TSUnionType':
|
||||||
try {
|
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 'TSSymbolKeyword':
|
||||||
|
return ['Symbol']
|
||||||
|
|
||||||
|
case 'TSIndexedAccessType': {
|
||||||
|
const types = resolveIndexType(ctx, node, scope)
|
||||||
|
return flattenTypes(ctx, types, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ClassDeclaration':
|
||||||
|
return ['Object']
|
||||||
|
|
||||||
|
case 'TSImportType': {
|
||||||
const sourceScope = importSourceToScope(
|
const sourceScope = importSourceToScope(
|
||||||
ctx,
|
ctx,
|
||||||
node.argument,
|
node.argument,
|
||||||
|
@ -1340,21 +1348,23 @@ export function inferRuntimeType(
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
|
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
break
|
||||||
break
|
}
|
||||||
}
|
|
||||||
|
case 'TSTypeQuery': {
|
||||||
case 'TSTypeQuery': {
|
const id = node.exprName
|
||||||
const id = node.exprName
|
if (id.type === 'Identifier') {
|
||||||
if (id.type === 'Identifier') {
|
// typeof only support identifier in local scope
|
||||||
// typeof only support identifier in local scope
|
const matched = scope.declares[id.name]
|
||||||
const matched = scope.declares[id.name]
|
if (matched) {
|
||||||
if (matched) {
|
return inferRuntimeType(ctx, matched, matched._ownerScope)
|
||||||
return inferRuntimeType(ctx, matched, matched._ownerScope)
|
}
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// always soft fail on failed runtime type inference
|
||||||
}
|
}
|
||||||
return [UNKNOWN_TYPE] // no runtime check
|
return [UNKNOWN_TYPE] // no runtime check
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue