diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 4fb9ecfdd..5f243b13e 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -443,6 +443,7 @@ export class JSOrTSDocumentSnapshot extends IdentityMapper implements DocumentSn private paramsPath = 'src/params'; private serverHooksPath = 'src/hooks.server'; private clientHooksPath = 'src/hooks.client'; + private universalHooksPath = 'src/hooks'; private openedByClient = false; @@ -578,7 +579,8 @@ export class JSOrTSDocumentSnapshot extends IdentityMapper implements DocumentSn { clientHooksPath: this.clientHooksPath, paramsPath: this.paramsPath, - serverHooksPath: this.serverHooksPath + serverHooksPath: this.serverHooksPath, + universalHooksPath: this.universalHooksPath }, () => this.createSource(), surroundWithIgnoreComments @@ -596,6 +598,7 @@ export class JSOrTSDocumentSnapshot extends IdentityMapper implements DocumentSn this.paramsPath ||= files.params; this.serverHooksPath ||= files.hooks?.server; this.clientHooksPath ||= files.hooks?.client; + this.universalHooksPath ||= files.hooks?.universal; } } diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts index 08b18f563..d9949e30f 100644 --- a/packages/svelte2tsx/index.d.ts +++ b/packages/svelte2tsx/index.d.ts @@ -135,16 +135,11 @@ export const internalHelpers: { options: InternalHelpers.KitFilesSettings ) => boolean; isKitRouteFile: (basename: string) => boolean, - isClientHooksFile: ( + isHooksFile: ( fileName: string, basename: string, - clientHooksPath: string - ) =>boolean, - isServerHooksFile: ( - fileName: string, - basename: string, - serverHooksPath: string - )=> boolean, + hooksPath: string + ) => boolean, isParamsFile: (fileName: string, basename: string, paramsPath: string) =>boolean, upsertKitFile: ( _ts: typeof ts, @@ -185,6 +180,7 @@ export namespace InternalHelpers { export interface KitFilesSettings { serverHooksPath: string; clientHooksPath: string; + universalHooksPath: string; paramsPath: string; } } diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 504253007..4c226dc9f 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.6.8", + "version": "0.7.0", "description": "Convert Svelte components to TSX for type checking", "author": "David Pershouse", "license": "MIT", diff --git a/packages/svelte2tsx/src/helpers/index.ts b/packages/svelte2tsx/src/helpers/index.ts index 83620923b..ab252d21c 100644 --- a/packages/svelte2tsx/src/helpers/index.ts +++ b/packages/svelte2tsx/src/helpers/index.ts @@ -1,9 +1,8 @@ import { - isClientHooksFile, + isHooksFile, isKitFile, isKitRouteFile, isParamsFile, - isServerHooksFile, toOriginalPos, toVirtualPos, upsertKitFile @@ -19,8 +18,7 @@ import { findExports } from './typescript'; export const internalHelpers = { isKitFile, isKitRouteFile, - isClientHooksFile, - isServerHooksFile, + isHooksFile, isParamsFile, upsertKitFile, toVirtualPos, diff --git a/packages/svelte2tsx/src/helpers/sveltekit.ts b/packages/svelte2tsx/src/helpers/sveltekit.ts index 2f907f0b4..92456d187 100644 --- a/packages/svelte2tsx/src/helpers/sveltekit.ts +++ b/packages/svelte2tsx/src/helpers/sveltekit.ts @@ -15,6 +15,7 @@ export interface AddedCode { export interface KitFilesSettings { serverHooksPath: string; clientHooksPath: string; + universalHooksPath: string; paramsPath: string; } @@ -27,8 +28,9 @@ export function isKitFile(fileName: string, options: KitFilesSettings): boolean const basename = path.basename(fileName); return ( isKitRouteFile(basename) || - isServerHooksFile(fileName, basename, options.serverHooksPath) || - isClientHooksFile(fileName, basename, options.clientHooksPath) || + isHooksFile(fileName, basename, options.serverHooksPath) || + isHooksFile(fileName, basename, options.clientHooksPath) || + isHooksFile(fileName, basename, options.universalHooksPath) || isParamsFile(fileName, basename, options.paramsPath) ); } @@ -50,30 +52,11 @@ export function isKitRouteFile(basename: string): boolean { /** * Determines whether or not a given file is a SvelteKit-specific hooks file */ -export function isServerHooksFile( - fileName: string, - basename: string, - serverHooksPath: string -): boolean { +export function isHooksFile(fileName: string, basename: string, hooksPath: string): boolean { return ( ((basename === 'index.ts' || basename === 'index.js') && - fileName.slice(0, -basename.length - 1).endsWith(serverHooksPath)) || - fileName.slice(0, -path.extname(basename).length).endsWith(serverHooksPath) - ); -} - -/** - * Determines whether or not a given file is a SvelteKit-specific hooks file - */ -export function isClientHooksFile( - fileName: string, - basename: string, - clientHooksPath: string -): boolean { - return ( - ((basename === 'index.ts' || basename === 'index.js') && - fileName.slice(0, -basename.length - 1).endsWith(clientHooksPath)) || - fileName.slice(0, -path.extname(basename).length).endsWith(clientHooksPath) + fileName.slice(0, -basename.length - 1).endsWith(hooksPath)) || + fileName.slice(0, -path.extname(basename).length).endsWith(hooksPath) ); } @@ -97,8 +80,8 @@ export function upsertKitFile( ): { text: string; addedCode: AddedCode[] } { let basename = path.basename(fileName); const result = - upserKitRouteFile(ts, basename, getSource, surround) ?? - upserKitServerHooksFile( + upsertKitRouteFile(ts, basename, getSource, surround) ?? + upsertKitServerHooksFile( ts, fileName, basename, @@ -106,7 +89,7 @@ export function upsertKitFile( getSource, surround ) ?? - upserKitClientHooksFile( + upsertKitClientHooksFile( ts, fileName, basename, @@ -114,7 +97,15 @@ export function upsertKitFile( getSource, surround ) ?? - upserKitParamsFile( + upsertKitUniversalHooksFile( + ts, + fileName, + basename, + kitFilesSettings.universalHooksPath, + getSource, + surround + ) ?? + upsertKitParamsFile( ts, fileName, basename, @@ -139,7 +130,7 @@ export function upsertKitFile( return { text, addedCode }; } -function upserKitRouteFile( +function upsertKitRouteFile( ts: _ts, basename: string, getSource: () => ts.SourceFile | undefined, @@ -225,7 +216,7 @@ function upserKitRouteFile( return { addedCode, originalText: source.getFullText() }; } -function upserKitParamsFile( +function upsertKitParamsFile( ts: _ts, fileName: string, basename: string, @@ -253,7 +244,7 @@ function upserKitParamsFile( return { addedCode, originalText: source.getFullText() }; } -function upserKitClientHooksFile( +function upsertKitClientHooksFile( ts: _ts, fileName: string, basename: string, @@ -261,7 +252,7 @@ function upserKitClientHooksFile( getSource: () => ts.SourceFile | undefined, surround: (text: string) => string ) { - if (!isClientHooksFile(fileName, basename, clientHooksPath)) { + if (!isHooksFile(fileName, basename, clientHooksPath)) { return; } @@ -288,7 +279,7 @@ function upserKitClientHooksFile( return { addedCode, originalText: source.getFullText() }; } -function upserKitServerHooksFile( +function upsertKitServerHooksFile( ts: _ts, fileName: string, basename: string, @@ -296,7 +287,7 @@ function upserKitServerHooksFile( getSource: () => ts.SourceFile | undefined, surround: (text: string) => string ) { - if (!isServerHooksFile(fileName, basename, serverHooksPath)) { + if (!isHooksFile(fileName, basename, serverHooksPath)) { return; } @@ -322,6 +313,34 @@ function upserKitServerHooksFile( return { addedCode, originalText: source.getFullText() }; } +function upsertKitUniversalHooksFile( + ts: _ts, + fileName: string, + basename: string, + universalHooksPath: string, + getSource: () => ts.SourceFile | undefined, + surround: (text: string) => string +) { + if (!isHooksFile(fileName, basename, universalHooksPath)) { + return; + } + + const source = getSource(); + if (!source) return; + + const addedCode: AddedCode[] = []; + const insert = (pos: number, inserted: string) => { + insertCode(addedCode, pos, inserted); + }; + + const isTsFile = basename.endsWith('.ts'); + const exports = findExports(ts, source, isTsFile); + + addTypeToFunction(ts, exports, surround, insert, 'reroute', `import('@sveltejs/kit').Reroute`); + + return { addedCode, originalText: source.getFullText() }; +} + function addTypeToVariable( exports: Map< string, diff --git a/packages/svelte2tsx/test/helpers/index.ts b/packages/svelte2tsx/test/helpers/index.ts index f120bae8f..3501429b7 100644 --- a/packages/svelte2tsx/test/helpers/index.ts +++ b/packages/svelte2tsx/test/helpers/index.ts @@ -11,7 +11,8 @@ describe('Internal Helpers - upsertKitFile', () => { { clientHooksPath: 'hooks.client', paramsPath: 'params', - serverHooksPath: 'hooks.server' + serverHooksPath: 'hooks.server', + universalHooksPath: 'hooks' }, () => sourceFile ); diff --git a/packages/typescript-plugin/src/language-service/diagnostics.ts b/packages/typescript-plugin/src/language-service/diagnostics.ts index d0d35cc68..4ff03c1e1 100644 --- a/packages/typescript-plugin/src/language-service/diagnostics.ts +++ b/packages/typescript-plugin/src/language-service/diagnostics.ts @@ -183,7 +183,8 @@ function getKitDiagnostics< messageText: `Invalid export '${exportName}' (valid exports are ${validExports.join( ', ' )}, or anything with a '_' prefix)`, - category: ts.DiagnosticCategory.Error, + // make it a warning in case people are stuck on old versions and new exports are added to SvelteKit + category: ts.DiagnosticCategory.Warning, code: 71001 // arbitrary }); } diff --git a/packages/typescript-plugin/src/language-service/hover.ts b/packages/typescript-plugin/src/language-service/hover.ts index 7e29850c1..dbf6fa76d 100644 --- a/packages/typescript-plugin/src/language-service/hover.ts +++ b/packages/typescript-plugin/src/language-service/hover.ts @@ -26,10 +26,8 @@ export function decorateHover( const node = source && findNodeAtPosition(source, virtualPos); if (node && isTopLevelExport(ts, node, source) && ts.isIdentifier(node)) { const name = node.text; - if (name in kitExports) { - quickInfo.documentation = !quickInfo.documentation?.length - ? kitExports[name].documentation - : quickInfo.documentation; + if (name in kitExports && !quickInfo.documentation?.length) { + quickInfo.documentation = kitExports[name].documentation; } } diff --git a/packages/typescript-plugin/src/language-service/sveltekit.ts b/packages/typescript-plugin/src/language-service/sveltekit.ts index b7c1e4945..0724ddef2 100644 --- a/packages/typescript-plugin/src/language-service/sveltekit.ts +++ b/packages/typescript-plugin/src/language-service/sveltekit.ts @@ -479,6 +479,19 @@ export const kitExports: Record< kind: 'text' } ] + }, + reroute: { + allowedIn: [], + displayParts: [], + documentation: [ + { + text: + `This function allows you to change how URLs are translated into routes. ` + + `The returned pathname (which defaults to url.pathname) is used to select the route and its parameters. ` + + `More info: https://kit.svelte.dev/docs/hooks#universal-hooks-reroute`, + kind: 'text' + } + ] } }; @@ -505,6 +518,13 @@ export function isKitRouteExportAllowedIn( ); } +const kitFilesSettings: InternalHelpers.KitFilesSettings = { + paramsPath: 'src/params', + clientHooksPath: 'src/hooks.client', + serverHooksPath: 'src/hooks.server', + universalHooksPath: 'src/hooks' +}; + function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, logger?: Logger) { const cachedProxiedLanguageService = cache.get(info); if (cachedProxiedLanguageService !== undefined) { @@ -521,35 +541,42 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo class ProxiedLanguageServiceHost implements ts.LanguageServiceHost { private files: Record = {}; - paramsPath = 'src/params'; - serverHooksPath = 'src/hooks.server'; - clientHooksPath = 'src/hooks.client'; constructor() { - const configPath = info.project.getCurrentDirectory() + '/svelte.config.js'; - import(configPath) - .then((module) => { - const config = module.default; - if (config.kit && config.kit.files) { - if (config.kit.files.params) { - this.paramsPath = config.kit.files.params; - } - if (config.kit.files.hooks) { - this.serverHooksPath ||= config.kit.files.hooks.server; - this.clientHooksPath ||= config.kit.files.hooks.client; - } - // We could be more sophisticated with only removing the files that are actually - // wrong but this is good enough given how rare it is that this setting is used - Object.keys(this.files) - .filter((name) => { - return !name.includes('src/hooks') && !name.includes('src/params'); - }) - .forEach((name) => { - delete this.files[name]; - }); - } - }) - .catch(() => {}); + // This never worked due to configPath being the wrong format and due to https://github.com/microsoft/TypeScript/issues/43329 . + // Noone has complained about this not working, so this is commented out for now, revisit if it ever comes up. + // const configPath = info.project.getCurrentDirectory() + '/svelte.config.js'; + // (import(configPath)) as Promise) + // .then((module) => { + // const config = module.default; + // if (config.kit && config.kit.files) { + // if (config.kit.files.params) { + // this.paramsPath = config.kit.files.params; + // } + // if (config.kit.files.hooks) { + // this.serverHooksPath ||= config.kit.files.hooks.server; + // this.clientHooksPath ||= config.kit.files.hooks.client; + // this.universalHooksPath ||= config.kit.files.hooks.universal; + // } + // logger?.log( + // `Using SvelteKit files config: ${JSON.stringify( + // config.kit.files.hooks + // )}` + // ); + // // We could be more sophisticated with only removing the files that are actually + // // wrong but this is good enough given how rare it is that this setting is used + // Object.keys(this.files) + // .filter((name) => { + // return !name.includes('src/hooks') && !name.includes('src/params'); + // }) + // .forEach((name) => { + // delete this.files[name]; + // }); + // } + // }) + // .catch((e) => { + // logger?.log('error loading SvelteKit file', e); + // }); } log() {} @@ -625,11 +652,7 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo const result = internalHelpers.upsertKitFile( ts, fileName, - { - clientHooksPath: this.clientHooksPath, - paramsPath: this.paramsPath, - serverHooksPath: this.serverHooksPath - }, + kitFilesSettings, () => info.languageService.getProgram()?.getSourceFile(fileName) ); if (!result) { @@ -705,7 +728,7 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo const languageServiceHost = new ProxiedLanguageServiceHost(); const languageService = ts.createLanguageService( languageServiceHost, - createProxyRegistry(ts, originalLanguageServiceHost, languageServiceHost) + createProxyRegistry(ts, originalLanguageServiceHost, kitFilesSettings) ); cache.set(info, { languageService, languageServiceHost }); return {