From: edison Date: Mon, 19 Jan 2026 00:42:25 +0000 (+0800) Subject: fix(compiler-sfc): use correct scope when resolving indexed access types from externa... X-Git-Tag: v3.5.27~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f0f0a21dfaf0f41bb2198106db0f4ff32b97dcd3;p=thirdparty%2Fvuejs%2Fcore.git fix(compiler-sfc): use correct scope when resolving indexed access types from external files (#14297) close #14292 --- diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 557f6ec5d9..4587d1c820 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -1136,6 +1136,64 @@ describe('resolveType', () => { expect(deps && [...deps]).toStrictEqual(Object.keys(files)) }) + test('relative import with indexed access type', () => { + const files = { + '/foo.ts': ` + type Booleanish = boolean | 'true' | 'false'; + export interface InputHTMLAttributes { + required?: Booleanish | undefined; + } + `, + } + const { props, deps } = resolve( + ` + import { InputHTMLAttributes } from './foo.ts' + type ImportedType = InputHTMLAttributes['required'] + defineProps<{ + required: ImportedType, + }>() + `, + files, + ) + expect(props).toStrictEqual({ + required: ['Boolean', 'String', 'Unknown'], + }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) + }) + + test('relative import with indexed access type with unresolvable extends', () => { + const files = { + '/foo.ts': ` + type EventHandlers = { + [K in keyof E]?: E[K] extends (...args: any) => any + ? E[K] + : (payload: E[K]) => void; + }; + export interface Events { + onCopy: ClipboardEvent; + } + type Booleanish = boolean | 'true' | 'false'; + export interface InputHTMLAttributes extends EventHandlers{ + required?: Booleanish | undefined; + } + `, + } + const { props, deps } = resolve( + ` + import { InputHTMLAttributes } from './foo.ts' + type ImportedType = InputHTMLAttributes['required'] + defineProps<{ + required: ImportedType, + }>() + `, + files, + ) + expect(props).toStrictEqual({ + required: ['Boolean', 'String', 'Unknown'], + }) + expect(deps && [...deps]).toStrictEqual(Object.keys(files)) + }) + // #8339 test('relative, .js import', () => { const files = { diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 9cb4ce8e7c..1cd4c08536 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -74,6 +74,7 @@ export type SimpleTypeResolveContext = Pick< // utils | 'error' + | 'warn' | 'helper' | 'getString' @@ -95,7 +96,12 @@ export type SimpleTypeResolveContext = Pick< options: SimpleTypeResolveOptions } -export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext +export type TypeResolveContext = ( + | ScriptCompileContext + | SimpleTypeResolveContext +) & { + silentOnExtendsFailure?: boolean +} type Import = Pick @@ -429,16 +435,21 @@ function resolveInterfaceMembers( ;(base.calls || (base.calls = [])).push(...calls) } } catch (e) { - ctx.error( - `Failed to resolve extends base type.\nIf this previously worked in 3.2, ` + - `you can instruct the compiler to ignore this extend by adding ` + - `/* @vue-ignore */ before it, for example:\n\n` + - `interface Props extends /* @vue-ignore */ Base {}\n\n` + - `Note: both in 3.2 or with the ignore, the properties in the base ` + - `type are treated as fallthrough attrs at runtime.`, - ext, - scope, - ) + // when called from inferRuntimeType context, silently ignore extends + // resolution failure so that properties defined in the interface can + // still be correctly resolved + if (!ctx.silentOnExtendsFailure) { + ctx.error( + `Failed to resolve extends base type.\nIf this previously worked in 3.2, ` + + `you can instruct the compiler to ignore this extend by adding ` + + `/* @vue-ignore */ before it, for example:\n\n` + + `interface Props extends /* @vue-ignore */ Base {}\n\n` + + `Note: both in 3.2 or with the ignore, the properties in the base ` + + `type are treated as fallthrough attrs at runtime.`, + ext, + scope, + ) + } } } } @@ -1519,6 +1530,10 @@ export function inferRuntimeType( return [UNKNOWN_TYPE] } + // set flag to silence extends resolution errors in this context + const prevSilent = ctx.silentOnExtendsFailure + ctx.silentOnExtendsFailure = true + try { switch (node.type) { case 'TSStringKeyword': @@ -1886,6 +1901,8 @@ export function inferRuntimeType( } } catch (e) { // always soft fail on failed runtime type inference + } finally { + ctx.silentOnExtendsFailure = prevSilent } return [UNKNOWN_TYPE] // no runtime check } @@ -1898,13 +1915,25 @@ function flattenTypes( typeParameters: Record | undefined = undefined, ): string[] { if (types.length === 1) { - return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters) + return inferRuntimeType( + ctx, + types[0], + (types[0] as MaybeWithScope)._ownerScope || scope, + isKeyOf, + typeParameters, + ) } return [ ...new Set( ([] as string[]).concat( ...types.map(t => - inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters), + inferRuntimeType( + ctx, + t, + (t as MaybeWithScope)._ownerScope || scope, + isKeyOf, + typeParameters, + ), ), ), ),