diff --git a/packages/language-server/src/plugins/svelte/SvelteDocument.ts b/packages/language-server/src/plugins/svelte/SvelteDocument.ts index 44ee3de46..65aa1146e 100644 --- a/packages/language-server/src/plugins/svelte/SvelteDocument.ts +++ b/packages/language-server/src/plugins/svelte/SvelteDocument.ts @@ -35,8 +35,8 @@ type PositionMapper = Pick | undefined; + private compileResult: Promise | undefined; private svelteVersion: [number, number] | undefined; public script: TagInformation | null; @@ -76,12 +76,12 @@ export class SvelteDocument { const [major, minor] = this.getSvelteVersion(); if (major > 3 || (major === 3 && minor >= 32)) { - this.transpiledDoc = await TranspiledSvelteDocument.create( + this.transpiledDoc = TranspiledSvelteDocument.create( this.parent, await this.config ); } else { - this.transpiledDoc = await FallbackTranspiledSvelteDocument.create( + this.transpiledDoc = FallbackTranspiledSvelteDocument.create( this.parent, (await this.config)?.preprocess ); @@ -92,7 +92,7 @@ export class SvelteDocument { async getCompiled(): Promise { if (!this.compileResult) { - this.compileResult = await this.getCompiledWith((await this.config)?.compilerOptions); + this.compileResult = this.getCompiledWith((await this.config)?.compilerOptions); } return this.compileResult; diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 6fd902108..7a9f1758c 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -1,6 +1,6 @@ { "name": "svelte2tsx", - "version": "0.7.23", + "version": "0.7.25", "description": "Convert Svelte components to TSX for type checking", "author": "David Pershouse", "license": "MIT", diff --git a/packages/svelte2tsx/src/helpers/files.ts b/packages/svelte2tsx/src/helpers/files.ts index a4bd1d2b4..b349a9fd6 100644 --- a/packages/svelte2tsx/src/helpers/files.ts +++ b/packages/svelte2tsx/src/helpers/files.ts @@ -12,17 +12,17 @@ export function get_global_types( typesPath: string, hiddenFolderPath?: string ): string[] { - const svelteHtmlPath = isSvelte3 ? undefined : join(sveltePath, 'svelte-html.d.ts'); - const svelteHtmlPathExists = svelteHtmlPath && tsSystem.fileExists(svelteHtmlPath); - const svelteHtmlFile = svelteHtmlPathExists ? svelteHtmlPath : './svelte-jsx-v4.d.ts'; + let svelteHtmlPath = isSvelte3 ? undefined : join(sveltePath, 'svelte-html.d.ts'); + svelteHtmlPath = + svelteHtmlPath && tsSystem.fileExists(svelteHtmlPath) ? svelteHtmlPath : undefined; let svelteTsxFiles: string[]; if (isSvelte3) { svelteTsxFiles = ['./svelte-shims.d.ts', './svelte-jsx.d.ts', './svelte-native-jsx.d.ts']; } else { svelteTsxFiles = ['./svelte-shims-v4.d.ts', './svelte-native-jsx.d.ts']; - if (!svelteHtmlPathExists) { - svelteTsxFiles.push(svelteHtmlPath); + if (!svelteHtmlPath) { + svelteTsxFiles.push('./svelte-jsx-v4.d.ts'); } } svelteTsxFiles = svelteTsxFiles.map((f) => tsSystem.resolvePath(resolve(typesPath, f))); @@ -53,10 +53,20 @@ export function get_global_types( for (const f of svelteTsxFiles) { const hiddenFile = resolve(hiddenPath, basename(f)); const existing = tsSystem.readFile(hiddenFile); - const toWrite = tsSystem.readFile(f) || ''; + const toWrite = tsSystem.readFile(f); + + if (!toWrite) { + throw new Error(`Could not read file: ${f}`); + } + if (existing !== toWrite) { tsSystem.writeFile(hiddenFile, toWrite); + // TS doesn't throw an error if the file wasn't written + if (!tsSystem.fileExists(hiddenFile)) { + throw new Error(`Could not write file: ${hiddenFile}`); + } } + newFiles.push(hiddenFile); } svelteTsxFiles = newFiles; @@ -64,8 +74,8 @@ export function get_global_types( } catch (e) {} } - if (svelteHtmlPathExists) { - svelteTsxFiles.push(tsSystem.resolvePath(resolve(typesPath, svelteHtmlFile))); + if (svelteHtmlPath) { + svelteTsxFiles.push(tsSystem.resolvePath(resolve(typesPath, svelteHtmlPath))); } return svelteTsxFiles; diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index 87e6779c4..2daf71419 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -16,7 +16,7 @@ import { SlotHandler } from './nodes/slot'; import { Stores } from './nodes/Stores'; import TemplateScope from './nodes/TemplateScope'; import { processInstanceScriptContent } from './processInstanceScriptContent'; -import { processModuleScriptTag } from './processModuleScriptTag'; +import { createModuleAst, ModuleAst, processModuleScriptTag } from './processModuleScriptTag'; import { ScopeStack } from './utils/Scope'; import { Generics } from './nodes/Generics'; import { addComponentExport } from './addComponentExport'; @@ -362,7 +362,11 @@ export function svelte2tsx( */ let instanceScriptTarget = 0; + let moduleAst: ModuleAst | undefined; + if (moduleScriptTag) { + moduleAst = createModuleAst(str, moduleScriptTag); + if (moduleScriptTag.start != 0) { //move our module tag to the top str.move(moduleScriptTag.start, moduleScriptTag.end, 0); @@ -398,7 +402,7 @@ export function svelte2tsx( events, implicitStoreValues, options.mode, - /**hasModuleScripts */ !!moduleScriptTag, + moduleAst, options?.isTsFile, basename, svelte5Plus, @@ -443,7 +447,8 @@ export function svelte2tsx( implicitStoreValues.getAccessedStores(), renderFunctionStart, scriptTag || options.mode === 'ts' ? undefined : (input) => `;${input}<>` - ) + ), + moduleAst ); } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index e4b05eaf4..b8851b23c 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -4,6 +4,7 @@ import { internalHelpers } from '../../helpers'; import { surroundWithIgnoreComments } from '../../utils/ignore'; import { preprendStr, overwriteStr } from '../../utils/magic-string'; import { findExportKeyword, getLastLeadingDoc, isInterfaceOrTypeDeclaration } from '../utils/tsAst'; +import { HoistableInterfaces } from './HoistableInterfaces'; export function is$$PropsDeclaration( node: ts.Node @@ -21,6 +22,7 @@ interface ExportedName { } export class ExportedNames { + public hoistableInterfaces = new HoistableInterfaces(); public usesAccessors = false; /** * Uses the `$$Props` type @@ -35,7 +37,9 @@ export class ExportedNames { * If using TS, this returns the generic string, if using JS, returns the `@type {..}` string. */ private $props = { + /** The JSDoc type; not set when TS type exists */ comment: '', + /** The TS type */ type: '', bindings: [] as string[] }; @@ -173,10 +177,13 @@ export class ExportedNames { } } + // Easy mode: User uses TypeScript and typed the $props() rune if (node.initializer.typeArguments?.length > 0 || node.type) { + this.hoistableInterfaces.analyze$propsRune(node); + const generic_arg = node.initializer.typeArguments?.[0] || node.type; const generic = generic_arg.getText(); - if (!generic.includes('{')) { + if (ts.isTypeReferenceNode(generic_arg)) { this.$props.type = generic; } else { // Create a virtual type alias for the unnamed generic and reuse it for the props return type @@ -199,13 +206,28 @@ export class ExportedNames { surroundWithIgnoreComments(this.$props.type) ); } - } else { - if (!this.isTsFile) { - const text = node.getSourceFile().getFullText(); - let start = -1; - let comment: string; - // reverse because we want to look at the last comment before the node first - for (const c of [...(ts.getLeadingCommentRanges(text, node.pos) || [])].reverse()) { + + return; + } + + // Hard mode: User uses JSDoc or didn't type the $props() rune + if (!this.isTsFile) { + const text = node.getSourceFile().getFullText(); + let start = -1; + let comment: string; + // reverse because we want to look at the last comment before the node first + for (const c of [...(ts.getLeadingCommentRanges(text, node.pos) || [])].reverse()) { + const potential_match = text.substring(c.pos, c.end); + if (/@type\b/.test(potential_match)) { + comment = potential_match; + start = c.pos + this.astOffset; + break; + } + } + if (!comment) { + for (const c of [ + ...(ts.getLeadingCommentRanges(text, node.parent.pos) || []).reverse() + ]) { const potential_match = text.substring(c.pos, c.end); if (/@type\b/.test(potential_match)) { comment = potential_match; @@ -213,142 +235,132 @@ export class ExportedNames { break; } } - if (!comment) { - for (const c of [ - ...(ts.getLeadingCommentRanges(text, node.parent.pos) || []).reverse() - ]) { - const potential_match = text.substring(c.pos, c.end); - if (/@type\b/.test(potential_match)) { - comment = potential_match; - start = c.pos + this.astOffset; - break; - } - } - } - - if (comment && /\/\*\*[^@]*?@type\s*{\s*{.*}\s*}\s*\*\//.test(comment)) { - // Create a virtual type alias for the unnamed generic and reuse it for the props return type - // so that rename, find references etc works seamlessly across components - this.$props.comment = '/** @type {$$ComponentProps} */'; - const type_start = this.str.original.indexOf('@type', start); - this.str.overwrite(type_start, type_start + 5, '@typedef'); - const end = this.str.original.indexOf('*/', start); - this.str.overwrite(end, end + 2, ' $$ComponentProps */' + this.$props.comment); - } else { - // Complex comment or simple `@type {AType}` comment which we just use as-is. - // For the former this means things like rename won't work properly across components. - this.$props.comment = comment || ''; - } } - if (this.$props.comment) { - return; + if (comment && /\/\*\*[^@]*?@type\s*{\s*{.*}\s*}\s*\*\//.test(comment)) { + // Create a virtual type alias for the unnamed generic and reuse it for the props return type + // so that rename, find references etc works seamlessly across components + this.$props.comment = '/** @type {$$ComponentProps} */'; + const type_start = this.str.original.indexOf('@type', start); + this.str.overwrite(type_start, type_start + 5, '@typedef'); + const end = this.str.original.indexOf('*/', start); + this.str.overwrite(end, end + 2, ' $$ComponentProps */' + this.$props.comment); + } else { + // Complex comment or simple `@type {AType}` comment which we just use as-is. + // For the former this means things like rename won't work properly across components. + this.$props.comment = comment || ''; } + } - // Do a best-effort to extract the props from the object literal - let propsStr = ''; - let withUnknown = false; - let props = []; - - const isKitRouteFile = internalHelpers.isKitRouteFile(this.basename); - const isKitLayoutFile = isKitRouteFile && this.basename.includes('layout'); - - if (ts.isObjectBindingPattern(node.name)) { - for (const element of node.name.elements) { - if ( - !ts.isIdentifier(element.name) || - (element.propertyName && !ts.isIdentifier(element.propertyName)) || - !!element.dotDotDotToken - ) { - withUnknown = true; - } else { - const name = element.propertyName - ? (element.propertyName as ts.Identifier).text - : element.name.text; - if (isKitRouteFile) { - if (name === 'data') { - props.push( - `data: import('./$types.js').${ - isKitLayoutFile ? 'LayoutData' : 'PageData' - }` - ); - } - if (name === 'form' && !isKitLayoutFile) { - props.push(`form: import('./$types.js').ActionData`); - } - } else if (element.initializer) { - const initializer = - ts.isCallExpression(element.initializer) && - ts.isIdentifier(element.initializer.expression) && - element.initializer.expression.text === '$bindable' - ? element.initializer.arguments[0] - : element.initializer; - const type = !initializer - ? 'unknown' - : ts.isAsExpression(initializer) - ? initializer.type.getText() - : ts.isStringLiteral(initializer) - ? 'string' - : ts.isNumericLiteral(initializer) - ? 'number' - : initializer.kind === ts.SyntaxKind.TrueKeyword || - initializer.kind === ts.SyntaxKind.FalseKeyword - ? 'boolean' - : ts.isIdentifier(initializer) && - initializer.text !== 'undefined' - ? `typeof ${initializer.text}` - : ts.isArrowFunction(initializer) - ? 'Function' - : ts.isObjectLiteralExpression(initializer) - ? 'Record' - : ts.isArrayLiteralExpression(initializer) - ? 'unknown[]' - : 'unknown'; - props.push(`${name}?: ${type}`); - } else { - props.push(`${name}: unknown`); + if (this.$props.comment) { + // User uses JsDoc + return; + } + + // Do a best-effort to extract the props from the object literal + let propsStr = ''; + let withUnknown = false; + let props = []; + + const isKitRouteFile = internalHelpers.isKitRouteFile(this.basename); + const isKitLayoutFile = isKitRouteFile && this.basename.includes('layout'); + + if (ts.isObjectBindingPattern(node.name)) { + for (const element of node.name.elements) { + if ( + !ts.isIdentifier(element.name) || + (element.propertyName && !ts.isIdentifier(element.propertyName)) || + !!element.dotDotDotToken + ) { + withUnknown = true; + } else { + const name = element.propertyName + ? (element.propertyName as ts.Identifier).text + : element.name.text; + if (isKitRouteFile) { + if (name === 'data') { + props.push( + `data: import('./$types.js').${ + isKitLayoutFile ? 'LayoutData' : 'PageData' + }` + ); } + if (name === 'form' && !isKitLayoutFile) { + props.push(`form: import('./$types.js').ActionData`); + } + } else if (element.initializer) { + const initializer = + ts.isCallExpression(element.initializer) && + ts.isIdentifier(element.initializer.expression) && + element.initializer.expression.text === '$bindable' + ? element.initializer.arguments[0] + : element.initializer; + + const type = !initializer + ? 'any' + : ts.isAsExpression(initializer) + ? initializer.type.getText() + : ts.isStringLiteral(initializer) + ? 'string' + : ts.isNumericLiteral(initializer) + ? 'number' + : initializer.kind === ts.SyntaxKind.TrueKeyword || + initializer.kind === ts.SyntaxKind.FalseKeyword + ? 'boolean' + : ts.isIdentifier(initializer) && + initializer.text !== 'undefined' + ? `typeof ${initializer.text}` + : ts.isArrowFunction(initializer) + ? 'Function' + : ts.isObjectLiteralExpression(initializer) + ? 'Record' + : ts.isArrayLiteralExpression(initializer) + ? 'any[]' + : 'any'; + + props.push(`${name}?: ${type}`); + } else { + props.push(`${name}: any`); } } + } - if (isKitLayoutFile) { - props.push(`children: import('svelte').Snippet`); - } + if (isKitLayoutFile) { + props.push(`children: import('svelte').Snippet`); + } - if (props.length > 0) { - propsStr = - `{ ${props.join(', ')} }` + - (withUnknown ? ' & Record' : ''); - } else if (withUnknown) { - propsStr = 'Record'; - } else { - propsStr = 'Record'; - } + if (props.length > 0) { + propsStr = + `{ ${props.join(', ')} }` + (withUnknown ? ' & Record' : ''); + } else if (withUnknown) { + propsStr = 'Record'; } else { - propsStr = 'Record'; + propsStr = 'Record'; } + } else { + propsStr = 'Record'; + } - // Create a virtual type alias for the unnamed generic and reuse it for the props return type - // so that rename, find references etc works seamlessly across components - if (this.isTsFile) { - this.$props.type = '$$ComponentProps'; - if (props.length > 0 || withUnknown) { - preprendStr( - this.str, - node.parent.pos + this.astOffset, - surroundWithIgnoreComments(`;type $$ComponentProps = ${propsStr};`) - ); - preprendStr(this.str, node.name.end + this.astOffset, `: ${this.$props.type}`); - } - } else { - this.$props.comment = '/** @type {$$ComponentProps} */'; - if (props.length > 0 || withUnknown) { - preprendStr( - this.str, - node.pos + this.astOffset, - `/** @typedef {${propsStr}} $$ComponentProps */${this.$props.comment}` - ); - } + // Create a virtual type alias for the unnamed generic and reuse it for the props return type + // so that rename, find references etc works seamlessly across components + if (this.isTsFile) { + this.$props.type = '$$ComponentProps'; + if (props.length > 0 || withUnknown) { + preprendStr( + this.str, + node.parent.pos + this.astOffset, + surroundWithIgnoreComments(`;type $$ComponentProps = ${propsStr};`) + ); + preprendStr(this.str, node.name.end + this.astOffset, `: ${this.$props.type}`); + } + } else { + this.$props.comment = '/** @type {$$ComponentProps} */'; + if (props.length > 0 || withUnknown) { + preprendStr( + this.str, + node.pos + this.astOffset, + `/** @typedef {${propsStr}} $$ComponentProps */${this.$props.comment}` + ); } } } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts new file mode 100644 index 000000000..4337c3503 --- /dev/null +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts @@ -0,0 +1,335 @@ +import ts from 'typescript'; +import MagicString from 'magic-string'; + +/** + * Collects all imports and module-level declarations to then find out which interfaces/types are hoistable. + */ +export class HoistableInterfaces { + private import_value_set: Set = new Set(); + private import_type_set: Set = new Set(); + private interface_map: Map< + string, + { type_deps: Set; value_deps: Set; node: ts.Node } + > = new Map(); + private props_interface = { + name: '', + node: null as ts.Node | null, + type_deps: new Set(), + value_deps: new Set() + }; + + analyzeModuleScriptNode(node: ts.Node) { + // Handle Import Declarations + if (ts.isImportDeclaration(node) && node.importClause) { + const is_type_only = node.importClause.isTypeOnly; + + if ( + node.importClause.namedBindings && + ts.isNamedImports(node.importClause.namedBindings) + ) { + node.importClause.namedBindings.elements.forEach((element) => { + const import_name = element.name.text; + if (is_type_only || element.isTypeOnly) { + this.import_type_set.add(import_name); + } else { + this.import_value_set.add(import_name); + } + }); + } + + // Handle default imports + if (node.importClause.name) { + const default_import = node.importClause.name.text; + if (is_type_only) { + this.import_type_set.add(default_import); + } else { + this.import_value_set.add(default_import); + } + } + + // Handle namespace imports + if ( + node.importClause.namedBindings && + ts.isNamespaceImport(node.importClause.namedBindings) + ) { + const namespace_import = node.importClause.namedBindings.name.text; + if (is_type_only) { + this.import_type_set.add(namespace_import); + } else { + this.import_value_set.add(namespace_import); + } + } + } + + // Handle top-level declarations + if (ts.isVariableStatement(node)) { + node.declarationList.declarations.forEach((declaration) => { + if (ts.isIdentifier(declaration.name)) { + this.import_value_set.add(declaration.name.text); + } + }); + } + + if (ts.isFunctionDeclaration(node) && node.name) { + this.import_value_set.add(node.name.text); + } + + if (ts.isClassDeclaration(node) && node.name) { + this.import_value_set.add(node.name.text); + } + + if (ts.isEnumDeclaration(node)) { + this.import_value_set.add(node.name.text); + } + + if (ts.isTypeAliasDeclaration(node)) { + this.import_type_set.add(node.name.text); + } + + if (ts.isInterfaceDeclaration(node)) { + this.import_type_set.add(node.name.text); + } + } + + analyzeInstanceScriptNode(node: ts.Node) { + // Handle Import Declarations + if (ts.isImportDeclaration(node) && node.importClause) { + const is_type_only = node.importClause.isTypeOnly; + + if ( + node.importClause.namedBindings && + ts.isNamedImports(node.importClause.namedBindings) + ) { + node.importClause.namedBindings.elements.forEach((element) => { + const import_name = element.name.text; + if (is_type_only) { + this.import_type_set.add(import_name); + } else { + this.import_value_set.add(import_name); + } + }); + } + + // Handle default imports + if (node.importClause.name) { + const default_import = node.importClause.name.text; + if (is_type_only) { + this.import_type_set.add(default_import); + } else { + this.import_value_set.add(default_import); + } + } + + // Handle namespace imports + if ( + node.importClause.namedBindings && + ts.isNamespaceImport(node.importClause.namedBindings) + ) { + const namespace_import = node.importClause.namedBindings.name.text; + if (is_type_only) { + this.import_type_set.add(namespace_import); + } else { + this.import_value_set.add(namespace_import); + } + } + } + + // Handle Interface Declarations + if (ts.isInterfaceDeclaration(node)) { + const interface_name = node.name.text; + const type_dependencies: Set = new Set(); + const value_dependencies: Set = new Set(); + const generics = node.typeParameters?.map((param) => param.name.text) ?? []; + + node.members.forEach((member) => { + if (ts.isPropertySignature(member) && member.type) { + this.collectTypeDependencies( + member.type, + type_dependencies, + value_dependencies, + generics + ); + } else if (ts.isIndexSignatureDeclaration(member)) { + this.collectTypeDependencies( + member.type, + type_dependencies, + value_dependencies, + generics + ); + member.parameters.forEach((param) => { + this.collectTypeDependencies( + param.type, + type_dependencies, + value_dependencies, + generics + ); + }); + } + }); + + this.interface_map.set(interface_name, { + type_deps: type_dependencies, + value_deps: value_dependencies, + node + }); + } + + // Handle Type Alias Declarations + if (ts.isTypeAliasDeclaration(node)) { + const alias_name = node.name.text; + const type_dependencies: Set = new Set(); + const value_dependencies: Set = new Set(); + const generics = node.typeParameters?.map((param) => param.name.text) ?? []; + + this.collectTypeDependencies( + node.type, + type_dependencies, + value_dependencies, + generics + ); + + this.interface_map.set(alias_name, { + type_deps: type_dependencies, + value_deps: value_dependencies, + node + }); + } + } + + analyze$propsRune( + node: ts.VariableDeclaration & { + initializer: ts.CallExpression & { expression: ts.Identifier }; + } + ) { + if (node.initializer.typeArguments?.length > 0 || node.type) { + const generic_arg = node.initializer.typeArguments?.[0] || node.type; + if (ts.isTypeReferenceNode(generic_arg)) { + const name = this.getEntityNameText(generic_arg.typeName); + const interface_node = this.interface_map.get(name); + if (interface_node) { + this.props_interface.name = name; + this.props_interface.type_deps = interface_node.type_deps; + this.props_interface.value_deps = interface_node.value_deps; + } + } else { + this.props_interface.name = '$$ComponentProps'; + this.props_interface.node = generic_arg; + this.collectTypeDependencies( + generic_arg, + this.props_interface.type_deps, + this.props_interface.value_deps, + [] + ); + } + } + } + + /** + * Traverses the AST to collect import statements and top-level interfaces, + * then determines which interfaces can be hoisted. + * @param source_file The TypeScript source file to analyze. + * @returns An object containing sets of value imports, type imports, and hoistable interfaces. + */ + private determineHoistableInterfaces() { + const hoistable_interfaces: Map = new Map(); + let progress = true; + + while (progress) { + progress = false; + + for (const [interface_name, deps] of this.interface_map.entries()) { + if (hoistable_interfaces.has(interface_name)) { + continue; + } + + const can_hoist = [...deps.type_deps, ...deps.value_deps].every((dep) => { + return ( + this.import_type_set.has(dep) || + this.import_value_set.has(dep) || + hoistable_interfaces.has(dep) + ); + }); + + if (can_hoist) { + hoistable_interfaces.set(interface_name, deps.node); + progress = true; + } + } + } + + if (this.props_interface.name === '$$ComponentProps') { + const can_hoist = [ + ...this.props_interface.type_deps, + ...this.props_interface.value_deps + ].every((dep) => { + return ( + this.import_type_set.has(dep) || + this.import_value_set.has(dep) || + hoistable_interfaces.has(dep) + ); + }); + + if (can_hoist) { + hoistable_interfaces.set(this.props_interface.name, this.props_interface.node); + } + } + + return hoistable_interfaces; + } + + /** + * Moves all interfaces that can be hoisted to the top of the script, if the $props rune's type is hoistable. + */ + moveHoistableInterfaces(str: MagicString, astOffset: number, scriptStart: number) { + if (!this.props_interface.name) return; + + const hoistable = this.determineHoistableInterfaces(); + if (hoistable.has(this.props_interface.name)) { + for (const [, node] of hoistable) { + str.move(node.pos + astOffset, node.end + astOffset, scriptStart); + } + } + } + + /** + * Collects type and value dependencies from a given TypeNode. + * @param type_node The TypeNode to analyze. + * @param type_dependencies The set to collect type dependencies into. + * @param value_dependencies The set to collect value dependencies into. + */ + private collectTypeDependencies( + type_node: ts.TypeNode, + type_dependencies: Set, + value_dependencies: Set, + generics: string[] + ) { + const walk = (node: ts.Node) => { + if (ts.isTypeReferenceNode(node)) { + const type_name = this.getEntityNameText(node.typeName); + if (!generics.includes(type_name)) { + type_dependencies.add(type_name); + } + } else if (ts.isTypeQueryNode(node)) { + // Handle 'typeof' expressions: e.g., foo: typeof bar + value_dependencies.add(this.getEntityNameText(node.exprName)); + } + + ts.forEachChild(node, walk); + }; + + walk(type_node); + } + + /** + * Retrieves the full text of an EntityName (handles nested names). + * @param entity_name The EntityName to extract text from. + * @returns The full name as a string. + */ + private getEntityNameText(entity_name: ts.EntityName): string { + if (ts.isIdentifier(entity_name)) { + return entity_name.text; + } else { + return this.getEntityNameText(entity_name.left) + '.' + entity_name.right.text; + } + } +} diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index 044aeb83a..e7d66870c 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -16,6 +16,7 @@ import { handleImportDeclaration } from './nodes/handleImportDeclaration'; import { InterfacesAndTypes } from './nodes/InterfacesAndTypes'; +import { ModuleAst } from './processModuleScriptTag'; export interface InstanceScriptProcessResult { exportedNames: ExportedNames; @@ -39,7 +40,7 @@ export function processInstanceScriptContent( events: ComponentEvents, implicitStoreValues: ImplicitStoreValues, mode: 'ts' | 'dts', - hasModuleScript: boolean, + moduleAst: ModuleAst | undefined, isTSFile: boolean, basename: string, isSvelte5Plus: boolean, @@ -66,6 +67,12 @@ export function processInstanceScriptContent( const generics = new Generics(str, astOffset, script); const interfacesAndTypes = new InterfacesAndTypes(); + if (moduleAst) { + moduleAst.tsAst.forEachChild((n) => + exportedNames.hoistableInterfaces.analyzeModuleScriptNode(n) + ); + } + const implicitTopLevelNames = new ImplicitTopLevelNames(str, astOffset); let uses$$props = false; let uses$$restProps = false; @@ -159,6 +166,10 @@ export function processInstanceScriptContent( type onLeaveCallback = () => void; const onLeaveCallbacks: onLeaveCallback[] = []; + if (parent === tsAst) { + exportedNames.hoistableInterfaces.analyzeInstanceScriptNode(node); + } + generics.addIfIsGeneric(node); if (is$$EventsDeclaration(node)) { @@ -290,7 +301,7 @@ export function processInstanceScriptContent( implicitTopLevelNames.modifyCode(rootScope.declared); implicitStoreValues.modifyCode(astOffset, str); - handleFirstInstanceImport(tsAst, astOffset, hasModuleScript, str); + handleFirstInstanceImport(tsAst, astOffset, !!moduleAst, str); // move interfaces and types out of the render function if they are referenced // by a $$Generic, otherwise it will be used before being defined after the transformation @@ -307,6 +318,8 @@ export function processInstanceScriptContent( transformInterfacesToTypes(tsAst, str, astOffset, nodesToMove); } + exportedNames.hoistableInterfaces.moveHoistableInterfaces(str, astOffset, script.start); + return { exportedNames, events, diff --git a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts index fa5f89a89..d66d4630b 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts @@ -9,11 +9,13 @@ import { throwError } from './utils/error'; import { is$$SlotsDeclaration } from './nodes/slot'; import { is$$PropsDeclaration } from './nodes/ExportedNames'; -export function processModuleScriptTag( - str: MagicString, - script: Node, - implicitStoreValues: ImplicitStoreValues -) { +export interface ModuleAst { + htmlx: string; + tsAst: ts.SourceFile; + astOffset: number; +} + +export function createModuleAst(str: MagicString, script: Node): ModuleAst { const htmlx = str.original; const scriptContent = htmlx.substring(script.content.start, script.content.end); const tsAst = ts.createSourceFile( @@ -25,6 +27,17 @@ export function processModuleScriptTag( ); const astOffset = script.content.start; + return { htmlx, tsAst, astOffset }; +} + +export function processModuleScriptTag( + str: MagicString, + script: Node, + implicitStoreValues: ImplicitStoreValues, + moduleAst: ModuleAst +) { + const { htmlx, tsAst, astOffset } = moduleAst; + const generics = new Generics(str, astOffset, script); if (generics.genericsAttr) { const start = htmlx.indexOf('generics', script.start); diff --git a/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts index 350d1d487..70e0a214d 100644 --- a/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts +++ b/packages/svelte2tsx/test/emitDts/samples/javascript-runes.v5/expected/TestRunes.svelte.d.ts @@ -1,7 +1,8 @@ -declare const TestRunes: import("svelte").Component<{ +type $$ComponentProps = { foo: string; bar?: number; -}, { +}; +declare const TestRunes: import("svelte").Component<$$ComponentProps, { baz: () => void; }, "bar">; type TestRunes = ReturnType; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts deleted file mode 100644 index 350d1d487..000000000 --- a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes.svelte.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare const TestRunes: import("svelte").Component<{ - foo: string; - bar?: number; -}, { - baz: () => void; -}, "bar">; -type TestRunes = ReturnType; -export default TestRunes; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes1.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes1.svelte.d.ts new file mode 100644 index 000000000..015490b7f --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes1.svelte.d.ts @@ -0,0 +1,9 @@ +type $$ComponentProps = { + foo: string; + bar?: number; +}; +declare const TestRunes1: import("svelte").Component<$$ComponentProps, { + baz: () => void; +}, "bar">; +type TestRunes1 = ReturnType; +export default TestRunes1; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes2.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes2.svelte.d.ts new file mode 100644 index 000000000..885a90203 --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/expected/TestRunes2.svelte.d.ts @@ -0,0 +1,9 @@ +/** asd */ +type Props = { + foo: string; + bar?: X; +}; +import type { X } from './x'; +declare const TestRunes2: import("svelte").Component; +type TestRunes2 = ReturnType; +export default TestRunes2; diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes.svelte b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes1.svelte similarity index 100% rename from packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes.svelte rename to packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes1.svelte diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes2.svelte b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes2.svelte new file mode 100644 index 000000000..c331da40d --- /dev/null +++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes.v5/src/TestRunes2.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts index 7404a44ab..72cffb46c 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-best-effort-types.v5/expectedv2.ts @@ -1,7 +1,7 @@ /// ;function render() { - let/** @typedef {{ a: unknown, b?: boolean, c?: number, d?: string, e?: unknown, f?: Record, g?: typeof foo, h?: unknown[], i?: unknown, j?: unknown, k?: number, l?: Function }} $$ComponentProps *//** @type {$$ComponentProps} */ { a, b = true, c = 1, d = '', e = null, f = {}, g = foo, h = [], i = undefined, j = $bindable(), k = $bindable(1), l = () => {} } = $props(); + let/** @typedef {{ a: any, b?: boolean, c?: number, d?: string, e?: any, f?: Record, g?: typeof foo, h?: any[], i?: any, j?: any, k?: number, l?: Function }} $$ComponentProps *//** @type {$$ComponentProps} */ { a, b = true, c = 1, d = '', e = null, f = {}, g = foo, h = [], i = undefined, j = $bindable(), k = $bindable(1), l = () => {} } = $props(); ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings('j', 'k'), slots: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts index 5379c6643..f392054d8 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-bindable.v5/expectedv2.ts @@ -1,7 +1,7 @@ /// ;function render() { - let/** @typedef {{ a: unknown, b?: unknown }} $$ComponentProps *//** @type {$$ComponentProps} */ { a, b = $bindable() } = $props(); + let/** @typedef {{ a: any, b?: any }} $$ComponentProps *//** @type {$$ComponentProps} */ { a, b = $bindable() } = $props(); ; async () => {}; return { props: /** @type {$$ComponentProps} */({}), exports: {}, bindings: __sveltets_$$bindings('b'), slots: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts index 2abbbff95..555322c0a 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/runes-looking-like-stores.v5/expectedv2.ts @@ -1,7 +1,7 @@ /// ;function render() { - let/** @typedef {{ props: unknown }} $$ComponentProps *//** @type {$$ComponentProps} */ { props } = $props(); + let/** @typedef {{ props: any }} $$ComponentProps *//** @type {$$ComponentProps} */ { props } = $props(); let state = $state(0); let derived = $derived(state * 2); ; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts index 455e1b9b6..b819a0c69 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-best-effort-types.v5/expectedv2.ts @@ -1,6 +1,6 @@ /// ;function render() { -/*Ωignore_startΩ*/;type $$ComponentProps = { a: unknown, b?: boolean, c?: number, d?: string, e?: unknown, f?: Record, g?: typeof foo, h?: Bar, i?: Baz, j?: unknown[], k?: unknown, l?: unknown, m?: number, n?: Function };/*Ωignore_endΩ*/ +/*Ωignore_startΩ*/;type $$ComponentProps = { a: any, b?: boolean, c?: number, d?: string, e?: any, f?: Record, g?: typeof foo, h?: Bar, i?: Baz, j?: any[], k?: any, l?: any, m?: number, n?: Function };/*Ωignore_endΩ*/ let { a, b = true, c = 1, d = '', e = null, f = {}, g = foo, h = null as Bar, i = null as any as Baz, j = [], k = undefined, l = $bindable(), m = $bindable(1), n = () => {} }: $$ComponentProps = $props(); ; async () => {}; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts index f2686212b..22c641cf8 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-bindable.v5/expectedv2.ts @@ -1,6 +1,6 @@ /// ;function render() { -/*Ωignore_startΩ*/;type $$ComponentProps = { a: unknown, b?: unknown, c?: number };/*Ωignore_endΩ*/ +/*Ωignore_startΩ*/;type $$ComponentProps = { a: any, b?: any, c?: number };/*Ωignore_endΩ*/ let { a, b = $bindable(), c = $bindable(0) as number }: $$ComponentProps = $props(); ; async () => {}; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-1.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-1.v5/expectedv2.ts new file mode 100644 index 000000000..c14fcdf06 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-1.v5/expectedv2.ts @@ -0,0 +1,27 @@ +/// +; + let value = 1; +; + type NoComma = true + type Dependency = { + a: number; + b: typeof value; + c: NoComma + } + + /** A comment */ + interface Props { + a: Dependency; + b: T; + };function render() { + + + let { a, b }: Props = $props(); +; +async () => { + +}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-1.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-1.v5/input.svelte new file mode 100644 index 000000000..9e642a6ea --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-1.v5/input.svelte @@ -0,0 +1,20 @@ + + + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts new file mode 100644 index 000000000..664f10006 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/expectedv2.ts @@ -0,0 +1,19 @@ +/// +; + let value = 1; +; + interface Dependency { + a: number; + b: typeof value; + };type $$ComponentProps = { a: Dependency, b: string };;function render() { + + + let { a, b }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props(); +; +async () => { + +}; +return { props: {} as any as $$ComponentProps, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/input.svelte new file mode 100644 index 000000000..958dc103e --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-2.v5/input.svelte @@ -0,0 +1,12 @@ + + + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-4.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-4.v5/expectedv2.ts new file mode 100644 index 000000000..1fedd1021 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-4.v5/expectedv2.ts @@ -0,0 +1,18 @@ +/// + + interface Dependency { + a: number; + } + + interface Props { + [k: string]: Dependency; + };function render() { + + + let { foo }: Props = $props(); +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-4.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-4.v5/input.svelte new file mode 100644 index 000000000..acf3829ba --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-4.v5/input.svelte @@ -0,0 +1,11 @@ + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-1.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-1.v5/expectedv2.ts new file mode 100644 index 000000000..abd2b797d --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-1.v5/expectedv2.ts @@ -0,0 +1,17 @@ +/// +;function render() { + + interface Props { + foo: C; + } + + const a = 1; + type C = typeof a | '2' | '3'; + + let { foo }: Props = $props(); +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-1.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-1.v5/input.svelte new file mode 100644 index 000000000..9de8cb5c2 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-1.v5/input.svelte @@ -0,0 +1,10 @@ + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-2.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-2.v5/expectedv2.ts new file mode 100644 index 000000000..9aaaa85e8 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-2.v5/expectedv2.ts @@ -0,0 +1,16 @@ +/// +;function render() { + + const a: string = ''; + + interface Props { + [index: typeof a]: boolean; + } + + let { foo }: Props = $props(); +; +async () => {}; +return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +const Input__SvelteComponent_ = __sveltets_2_fn_component(render()); +type Input__SvelteComponent_ = ReturnType; +export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-2.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-2.v5/input.svelte new file mode 100644 index 000000000..38a5ded93 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-2.v5/input.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts index e248ca3cf..b9ee4758c 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes.v5/expectedv2.ts @@ -1,6 +1,6 @@ /// -;function render() { -;type $$ComponentProps = { a: number, b: string }; +;type $$ComponentProps = { a: number, b: string };;function render() { + let { a, b }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props(); let x = $state(0); let y = $derived(x * 2); diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts index 04b6fd6ba..2bdf2e4ea 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts @@ -1,7 +1,7 @@ /// -;function render() { +;type $$ComponentProps = {form: boolean, data: true };;function render() { - const snapshot: any = {};;type $$ComponentProps = {form: boolean, data: true }; + const snapshot: any = {}; let { form, data }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props(); ; async () => {}; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy