Skip to content

Commit f0f0a21

Browse files
authored
fix(compiler-sfc): use correct scope when resolving indexed access types from external files (#14297)
close #14292
1 parent cde5d8f commit f0f0a21

2 files changed

Lines changed: 100 additions & 13 deletions

File tree

packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,64 @@ describe('resolveType', () => {
11361136
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
11371137
})
11381138

1139+
test('relative import with indexed access type', () => {
1140+
const files = {
1141+
'/foo.ts': `
1142+
type Booleanish = boolean | 'true' | 'false';
1143+
export interface InputHTMLAttributes {
1144+
required?: Booleanish | undefined;
1145+
}
1146+
`,
1147+
}
1148+
const { props, deps } = resolve(
1149+
`
1150+
import { InputHTMLAttributes } from './foo.ts'
1151+
type ImportedType = InputHTMLAttributes['required']
1152+
defineProps<{
1153+
required: ImportedType,
1154+
}>()
1155+
`,
1156+
files,
1157+
)
1158+
expect(props).toStrictEqual({
1159+
required: ['Boolean', 'String', 'Unknown'],
1160+
})
1161+
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
1162+
})
1163+
1164+
test('relative import with indexed access type with unresolvable extends', () => {
1165+
const files = {
1166+
'/foo.ts': `
1167+
type EventHandlers<E> = {
1168+
[K in keyof E]?: E[K] extends (...args: any) => any
1169+
? E[K]
1170+
: (payload: E[K]) => void;
1171+
};
1172+
export interface Events {
1173+
onCopy: ClipboardEvent;
1174+
}
1175+
type Booleanish = boolean | 'true' | 'false';
1176+
export interface InputHTMLAttributes extends EventHandlers<Events>{
1177+
required?: Booleanish | undefined;
1178+
}
1179+
`,
1180+
}
1181+
const { props, deps } = resolve(
1182+
`
1183+
import { InputHTMLAttributes } from './foo.ts'
1184+
type ImportedType = InputHTMLAttributes['required']
1185+
defineProps<{
1186+
required: ImportedType,
1187+
}>()
1188+
`,
1189+
files,
1190+
)
1191+
expect(props).toStrictEqual({
1192+
required: ['Boolean', 'String', 'Unknown'],
1193+
})
1194+
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
1195+
})
1196+
11391197
// #8339
11401198
test('relative, .js import', () => {
11411199
const files = {

packages/compiler-sfc/src/script/resolveType.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export type SimpleTypeResolveContext = Pick<
7474

7575
// utils
7676
| 'error'
77+
| 'warn'
7778
| 'helper'
7879
| 'getString'
7980

@@ -95,7 +96,12 @@ export type SimpleTypeResolveContext = Pick<
9596
options: SimpleTypeResolveOptions
9697
}
9798

98-
export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext
99+
export type TypeResolveContext = (
100+
| ScriptCompileContext
101+
| SimpleTypeResolveContext
102+
) & {
103+
silentOnExtendsFailure?: boolean
104+
}
99105

100106
type Import = Pick<ImportBinding, 'source' | 'imported'>
101107

@@ -429,16 +435,21 @@ function resolveInterfaceMembers(
429435
;(base.calls || (base.calls = [])).push(...calls)
430436
}
431437
} catch (e) {
432-
ctx.error(
433-
`Failed to resolve extends base type.\nIf this previously worked in 3.2, ` +
434-
`you can instruct the compiler to ignore this extend by adding ` +
435-
`/* @vue-ignore */ before it, for example:\n\n` +
436-
`interface Props extends /* @vue-ignore */ Base {}\n\n` +
437-
`Note: both in 3.2 or with the ignore, the properties in the base ` +
438-
`type are treated as fallthrough attrs at runtime.`,
439-
ext,
440-
scope,
441-
)
438+
// when called from inferRuntimeType context, silently ignore extends
439+
// resolution failure so that properties defined in the interface can
440+
// still be correctly resolved
441+
if (!ctx.silentOnExtendsFailure) {
442+
ctx.error(
443+
`Failed to resolve extends base type.\nIf this previously worked in 3.2, ` +
444+
`you can instruct the compiler to ignore this extend by adding ` +
445+
`/* @vue-ignore */ before it, for example:\n\n` +
446+
`interface Props extends /* @vue-ignore */ Base {}\n\n` +
447+
`Note: both in 3.2 or with the ignore, the properties in the base ` +
448+
`type are treated as fallthrough attrs at runtime.`,
449+
ext,
450+
scope,
451+
)
452+
}
442453
}
443454
}
444455
}
@@ -1519,6 +1530,10 @@ export function inferRuntimeType(
15191530
return [UNKNOWN_TYPE]
15201531
}
15211532

1533+
// set flag to silence extends resolution errors in this context
1534+
const prevSilent = ctx.silentOnExtendsFailure
1535+
ctx.silentOnExtendsFailure = true
1536+
15221537
try {
15231538
switch (node.type) {
15241539
case 'TSStringKeyword':
@@ -1886,6 +1901,8 @@ export function inferRuntimeType(
18861901
}
18871902
} catch (e) {
18881903
// always soft fail on failed runtime type inference
1904+
} finally {
1905+
ctx.silentOnExtendsFailure = prevSilent
18891906
}
18901907
return [UNKNOWN_TYPE] // no runtime check
18911908
}
@@ -1898,13 +1915,25 @@ function flattenTypes(
18981915
typeParameters: Record<string, Node> | undefined = undefined,
18991916
): string[] {
19001917
if (types.length === 1) {
1901-
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
1918+
return inferRuntimeType(
1919+
ctx,
1920+
types[0],
1921+
(types[0] as MaybeWithScope)._ownerScope || scope,
1922+
isKeyOf,
1923+
typeParameters,
1924+
)
19021925
}
19031926
return [
19041927
...new Set(
19051928
([] as string[]).concat(
19061929
...types.map(t =>
1907-
inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
1930+
inferRuntimeType(
1931+
ctx,
1932+
t,
1933+
(t as MaybeWithScope)._ownerScope || scope,
1934+
isKeyOf,
1935+
typeParameters,
1936+
),
19081937
),
19091938
),
19101939
),

0 commit comments

Comments
 (0)