diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 816d74bc649..4ced9b8879a 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -1434,6 +1434,29 @@ describe('resolveType', () => { colsLg: ['Number'], }) }) + + test('allowArbitraryExtensions', () => { + const files = { + '/foo.d.vue.ts': 'export type Foo = number;', + '/foo.vue': '', + '/bar.d.css.ts': 'export type Bar = string;', + '/bar.css': ':root { --color: red; }', + } + + const { props } = resolve( + ` + import { Foo } from './foo.vue' + import { Bar } from './bar.css' + defineProps<{ foo: Foo; bar: Bar }>() + `, + files, + ) + + expect(props).toStrictEqual({ + foo: ['Number'], + bar: ['String'], + }) + }) }) }) diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 6bb647f11ff..9d785f38d5f 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -860,13 +860,13 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined { } return (ctx.fs = { fileExists(file) { - if (file.endsWith('.vue.ts')) { + if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) { file = file.replace(/\.ts$/, '') } return fs.fileExists(file) }, readFile(file) { - if (file.endsWith('.vue.ts')) { + if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) { file = file.replace(/\.ts$/, '') } return fs.readFile(file) @@ -1059,7 +1059,7 @@ function resolveWithTS( if (res.resolvedModule) { let filename = res.resolvedModule.resolvedFileName - if (filename.endsWith('.vue.ts')) { + if (filename.endsWith('.vue.ts') && !filename.endsWith('.d.vue.ts')) { filename = filename.replace(/\.ts$/, '') } return fs.realpath ? fs.realpath(filename) : filename @@ -1129,7 +1129,7 @@ export function fileToScope( // fs should be guaranteed to exist here const fs = resolveFS(ctx)! const source = fs.readFile(filename) || '' - const body = parseFile(filename, source, ctx.options.babelParserPlugins) + const body = parseFile(filename, source, fs, ctx.options.babelParserPlugins) const scope = new TypeScope(filename, source, 0, recordImports(body)) recordTypes(ctx, body, scope, asGlobal) fileToScopeCache.set(filename, scope) @@ -1139,6 +1139,7 @@ export function fileToScope( function parseFile( filename: string, content: string, + fs: FS, parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'], ): Statement[] { const ext = extname(filename) @@ -1151,7 +1152,21 @@ function parseFile( ), sourceType: 'module', }).program.body - } else if (ext === '.vue') { + } + + // simulate `allowArbitraryExtensions` on TypeScript >= 5.0 + const isUnknownTypeSource = !/\.[cm]?[tj]sx?$/.test(filename) + const arbitraryTypeSource = `${filename.slice(0, -ext.length)}.d${ext}.ts` + const hasArbitraryTypeDeclaration = + isUnknownTypeSource && fs.fileExists(arbitraryTypeSource) + if (hasArbitraryTypeDeclaration) { + return babelParse(fs.readFile(arbitraryTypeSource)!, { + plugins: resolveParserPlugins('ts', parserPlugins, true), + sourceType: 'module', + }).program.body + } + + if (ext === '.vue') { const { descriptor: { script, scriptSetup }, } = parse(content)