svelte2tsx 0.7.46 → 0.7.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.d.ts +6 -0
  2. package/index.js +170 -62
  3. package/index.mjs +170 -62
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -84,6 +84,12 @@ export function svelte2tsx(
84
84
  * Transpiled output may vary between versions.
85
85
  */
86
86
  version?: string;
87
+ /**
88
+ * If true, emits JSDoc annotations for types in JS files instead of TypeScript syntax.
89
+ * This is useful for svelte-check's incremental mode where the output needs to be
90
+ * valid JS that tsc can process without errors.
91
+ */
92
+ emitJsDoc?: boolean;
87
93
  }
88
94
  ): SvelteCompiledToTsx
89
95
 
package/index.js CHANGED
@@ -2839,7 +2839,7 @@ const supportsBindThis = [
2839
2839
  /**
2840
2840
  * Transform bind:xxx into something that conforms to JS/TS
2841
2841
  */
2842
- function handleBinding(str, attr, parent, element, preserveBind, isSvelte5Plus) {
2842
+ function handleBinding(str, attr, parent, element, preserveBind, isSvelte5Plus, emitJsDoc, isTsFile) {
2843
2843
  const isGetSetBinding = attr.expression.type === 'SequenceExpression';
2844
2844
  const [get, set] = isGetSetBinding ? attr.expression.expressions : [];
2845
2845
  // bind this
@@ -2871,9 +2871,14 @@ function handleBinding(str, attr, parent, element, preserveBind, isSvelte5Plus)
2871
2871
  }
2872
2872
  // one way binding whose property is not on the element
2873
2873
  if (oneWayBindingAttributesNotOnElement.has(attr.name) && element instanceof Element) {
2874
+ const useTypescriptSyntax = isTsFile || !emitJsDoc;
2875
+ const bindingType = oneWayBindingAttributesNotOnElement.get(attr.name);
2876
+ const bindingValue = useTypescriptSyntax
2877
+ ? `null as ${bindingType}`
2878
+ : `/** @type {${bindingType}} */ (null)`;
2874
2879
  element.appendToStartEnd([
2875
2880
  [attr.expression.start, getEnd(attr.expression)],
2876
- `= ${surroundWithIgnoreComments(`null as ${oneWayBindingAttributesNotOnElement.get(attr.name)}`)};`
2881
+ `= ${surroundWithIgnoreComments(bindingValue)};`
2877
2882
  ]);
2878
2883
  return;
2879
2884
  }
@@ -3338,10 +3343,11 @@ function handleTransitionDirective(str, attr, element) {
3338
3343
  * }
3339
3344
  * ```
3340
3345
  */
3341
- function handleSnippet(str, snippetBlock, component) {
3346
+ function handleSnippet(str, snippetBlock, component, emitJsDoc = false, isTsFile = false) {
3342
3347
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3343
3348
  const isImplicitProp = component !== undefined;
3344
3349
  const endSnippet = str.original.lastIndexOf('{', snippetBlock.end - 1);
3350
+ const usTSSyntax = isTsFile || !emitJsDoc;
3345
3351
  const afterSnippet = isImplicitProp
3346
3352
  ? `};return __sveltets_2_any(0)}`
3347
3353
  : `};return __sveltets_2_any(0)};`;
@@ -3407,13 +3413,14 @@ function handleSnippet(str, snippetBlock, component) {
3407
3413
  'const ',
3408
3414
  [snippetBlock.expression.start, snippetBlock.expression.end],
3409
3415
  IGNORE_POSITION_COMMENT,
3410
- ` = ${snippetBlock.typeParams ? `<${snippetBlock.typeParams}>` : ''}(`
3416
+ ` = ${usTSSyntax ? '' : "/** @returns {ReturnType<import('svelte').Snippet>} */ "}${usTSSyntax && snippetBlock.typeParams ? `<${snippetBlock.typeParams}>` : ''}(`
3411
3417
  ];
3412
3418
  if (parameters) {
3413
3419
  transforms.push(parameters);
3414
3420
  }
3415
- transforms.push(')', surroundWithIgnoreComments(`: ReturnType<import('svelte').Snippet>`), // shows up nicely preserved on hover, other alternatives don't
3416
- afterParameters);
3421
+ transforms.push(')', usTSSyntax
3422
+ ? surroundWithIgnoreComments(`: ReturnType<import('svelte').Snippet>`) // shows up nicely preserved on hover, other alternatives don't
3423
+ : '', afterParameters);
3417
3424
  transform(str, snippetBlock.start, startEnd, transforms);
3418
3425
  }
3419
3426
  }
@@ -4895,8 +4902,11 @@ function handleAttachTag(tag, element) {
4895
4902
  * and converts it to JSX
4896
4903
  */
4897
4904
  function convertHtmlxToJsx(str, ast, tags, options = { svelte5Plus: false }) {
4905
+ var _a, _b;
4898
4906
  options.typingsNamespace = options.typingsNamespace || 'svelteHTML';
4899
4907
  const preserveAttributeCase = options.namespace === 'foreign';
4908
+ const emitJsDoc = (_a = options.emitJsDoc) !== null && _a !== void 0 ? _a : false;
4909
+ const isTsFile = (_b = options.isTsFile) !== null && _b !== void 0 ? _b : false;
4900
4910
  const rootSnippets = [];
4901
4911
  let element;
4902
4912
  const pendingSnippetHoistCheck = new Set();
@@ -5032,7 +5042,7 @@ function convertHtmlxToJsx(str, ast, tags, options = { svelte5Plus: false }) {
5032
5042
  (element instanceof Element &&
5033
5043
  element.tagName === 'svelte:boundary')
5034
5044
  ? element
5035
- : undefined);
5045
+ : undefined, emitJsDoc, isTsFile);
5036
5046
  if (parent === ast) {
5037
5047
  // root snippet -> move to instance script or possibly even module script
5038
5048
  const result = analyze({
@@ -5128,7 +5138,7 @@ function convertHtmlxToJsx(str, ast, tags, options = { svelte5Plus: false }) {
5128
5138
  handleComment(str, node);
5129
5139
  break;
5130
5140
  case 'Binding':
5131
- handleBinding(str, node, parent, element, options.typingsNamespace === 'svelteHTML', options.svelte5Plus);
5141
+ handleBinding(str, node, parent, element, options.typingsNamespace === 'svelteHTML', options.svelte5Plus, emitJsDoc, isTsFile);
5132
5142
  break;
5133
5143
  case 'Class':
5134
5144
  handleClassDirective(str, node, element);
@@ -5711,21 +5721,33 @@ function addComponentExport(params) {
5711
5721
  addSimpleComponentExport(params);
5712
5722
  }
5713
5723
  }
5714
- function addGenericsComponentExport({ events, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, generics, usesSlots, isSvelte5, noSvelteComponentTyped, hasTopLevelAwait }) {
5724
+ function addGenericsComponentExport({ events, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, isTsFile, generics, usesSlots, isSvelte5, noSvelteComponentTyped, hasTopLevelAwait, emitJsDoc }) {
5715
5725
  const genericsDef = generics.toDefinitionString();
5716
5726
  const genericsRef = generics.toReferencesString();
5727
+ const useTSSyntax = isTsFile || !emitJsDoc;
5728
+ const templateNames = !useTSSyntax && genericsDef
5729
+ ? genericsDef
5730
+ .slice(1, -1)
5731
+ .split(',')
5732
+ .map((part) => part.trim())
5733
+ .map((part) => part.split(/\s|=/)[0])
5734
+ .filter(Boolean)
5735
+ : [];
5736
+ const templateComment = templateNames.length
5737
+ ? `/** @template ${templateNames.join(', ')} */\n`
5738
+ : '';
5717
5739
  const doc = componentDocumentation.getFormatted();
5718
5740
  const className = fileName && classNameFromFilename(fileName, mode !== 'dts');
5719
5741
  function returnType(forPart) {
5720
5742
  return `ReturnType<__sveltets_Render${genericsRef}['${forPart}']>`;
5721
5743
  }
5722
5744
  const renderCall = hasTopLevelAwait
5723
- ? `(await ${internalHelpers.renderName}${genericsRef}())`
5724
- : `${internalHelpers.renderName}${genericsRef}()`;
5745
+ ? `(await ${internalHelpers.renderName}${useTSSyntax ? genericsRef : ''}())`
5746
+ : `${internalHelpers.renderName}${useTSSyntax ? genericsRef : ''}()`;
5725
5747
  // TODO once Svelte 4 compatibility is dropped, we can simplify this, because since TS 4.7 it is possible to use generics
5726
5748
  // like this: `typeof render<T>` - which wasn't possibly before, hence the class + methods workaround.
5727
5749
  let statement = `
5728
- class __sveltets_Render${genericsDef} {
5750
+ ${templateComment}class __sveltets_Render${useTSSyntax ? genericsDef : ''} {
5729
5751
  props() {
5730
5752
  return ${props(true, canHaveAnyProp, exportedNames, renderCall)}.props;
5731
5753
  }
@@ -5741,12 +5763,25 @@ class __sveltets_Render${genericsDef} {
5741
5763
  const renderType = hasTopLevelAwait
5742
5764
  ? `Awaited<ReturnType<typeof ${internalHelpers.renderName}${genericsRef}>>`
5743
5765
  : `ReturnType<typeof ${internalHelpers.renderName}${genericsRef}>`;
5744
- statement = `
5766
+ if (useTSSyntax) {
5767
+ statement = `
5745
5768
  class __sveltets_Render${genericsDef} {
5746
5769
  props(): ${renderType}['props'] { return null as any; }
5747
5770
  events(): ${renderType}['events'] { return null as any; }
5748
5771
  slots(): ${renderType}['slots'] { return null as any; }
5749
5772
  `;
5773
+ }
5774
+ else {
5775
+ statement = `
5776
+ ${templateComment}class __sveltets_Render {
5777
+ /** @returns {${renderType}['props']} */
5778
+ props() { return /** @type {any} */ (null); }
5779
+ /** @returns {${renderType}['events']} */
5780
+ events() { return /** @type {any} */ (null); }
5781
+ /** @returns {${renderType}['slots']} */
5782
+ slots() { return /** @type {any} */ (null); }
5783
+ `;
5784
+ }
5750
5785
  }
5751
5786
  statement += isSvelte5
5752
5787
  ? ` bindings() { return ${exportedNames.createBindingsStr()}; }
@@ -5778,15 +5813,29 @@ class __sveltets_Render${genericsDef} {
5778
5813
  // - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes
5779
5814
  // - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc)
5780
5815
  // TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then.
5781
- statement +=
5782
- `\ninterface $$IsomorphicComponent {\n` +
5783
- ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` +
5784
- ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` +
5785
- ` z_$$bindings?: ${bindingsType};\n` +
5786
- `}\n` +
5787
- `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` +
5788
- surroundWithIgnoreComments(`type ${className || '$$Component'}${genericsDef} = InstanceType<typeof ${className || '$$Component'}${genericsRef}>;\n`) +
5789
- `export default ${className || '$$Component'};`;
5816
+ if (useTSSyntax) {
5817
+ statement +=
5818
+ `\ninterface $$IsomorphicComponent {\n` +
5819
+ ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` +
5820
+ ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` +
5821
+ ` z_$$bindings?: ${bindingsType};\n` +
5822
+ `}\n` +
5823
+ `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` +
5824
+ surroundWithIgnoreComments(`type ${className || '$$Component'}${genericsDef} = InstanceType<typeof ${className || '$$Component'}${genericsRef}>;\n`) +
5825
+ `export default ${className || '$$Component'};`;
5826
+ }
5827
+ else {
5828
+ const componentName = className || '$$Component';
5829
+ const componentType = `(new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>) => import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')}) & ` +
5830
+ `(${genericsDef}(internal: unknown, props: ${propsType}) => ${returnType('exports')}) & ` +
5831
+ `{z_$$bindings?: ${bindingsType}}`;
5832
+ statement +=
5833
+ `\n${templateComment}` +
5834
+ `/** @typedef {${componentType}} $$IsomorphicComponent */\n` +
5835
+ `${doc}/** @type {$$IsomorphicComponent} */ export const ${componentName} = /** @type {any} */(null);\n` +
5836
+ `/** @typedef {InstanceType<typeof ${componentName}>} ${componentName} */\n` +
5837
+ `export default ${componentName};`;
5838
+ }
5790
5839
  }
5791
5840
  else if (mode === 'dts') {
5792
5841
  statement +=
@@ -5799,16 +5848,30 @@ class __sveltets_Render${genericsDef} {
5799
5848
  '\n}';
5800
5849
  }
5801
5850
  else {
5802
- statement +=
5803
- `\n\nimport { ${svelteComponentClass} as __SvelteComponentTyped__ } from "svelte" \n` +
5804
- `${doc}export default class${className ? ` ${className}` : ''}${genericsDef} extends __SvelteComponentTyped__<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> {` +
5805
- exportedNames.createClassGetters(genericsRef) +
5806
- (usesAccessors ? exportedNames.createClassAccessors() : '') +
5807
- '\n}';
5851
+ if (useTSSyntax) {
5852
+ statement +=
5853
+ `\n\nimport { ${svelteComponentClass} as __SvelteComponentTyped__ } from "svelte" \n` +
5854
+ `${doc}export default class${className ? ` ${className}` : ''}${genericsDef} extends __SvelteComponentTyped__<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> {` +
5855
+ exportedNames.createClassGetters(genericsRef) +
5856
+ (usesAccessors ? exportedNames.createClassAccessors() : '') +
5857
+ '\n}';
5858
+ }
5859
+ else {
5860
+ statement +=
5861
+ `\n\nimport { ${svelteComponentClass} as __SvelteComponentTyped__ } from "svelte" \n` +
5862
+ `/**` +
5863
+ templateNames.map((t) => ` * @template ${t}\n`).join('') +
5864
+ ` * @extends {__SvelteComponentTyped__<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}>}\n` +
5865
+ ` */\n` +
5866
+ `export default class${className ? ` ${className}` : ''} extends __SvelteComponentTyped__ {` +
5867
+ exportedNames.createClassGetters(genericsRef) +
5868
+ (usesAccessors ? exportedNames.createClassAccessors() : '') +
5869
+ '\n}';
5870
+ }
5808
5871
  }
5809
5872
  str.append(statement);
5810
5873
  }
5811
- function addSimpleComponentExport({ events, isTsFile, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, usesSlots, noSvelteComponentTyped, isSvelte5, hasTopLevelAwait }) {
5874
+ function addSimpleComponentExport({ events, isTsFile, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, usesSlots, noSvelteComponentTyped, isSvelte5, hasTopLevelAwait, emitJsDoc }) {
5812
5875
  const renderCall = hasTopLevelAwait
5813
5876
  ? `$${internalHelpers.renderName}`
5814
5877
  : `${internalHelpers.renderName}()`;
@@ -5885,18 +5948,25 @@ declare function $$__sveltets_2_isomorphic_component<
5885
5948
  }
5886
5949
  else {
5887
5950
  if (isSvelte5) {
5951
+ // When emitting JSDoc (JS files in incremental mode), we use @typedef instead of
5952
+ // TS type syntax, and must export the const so declaration-merging with @typedef works.
5953
+ const useTypeScriptSyntax = isTsFile || !emitJsDoc;
5888
5954
  if (exportedNames.isRunesMode() && !usesSlots && !events.hasEvents()) {
5889
5955
  statement =
5890
- `\n${awaitDeclaration}${doc}const ${componentName} = __sveltets_2_fn_component(${renderCall});\n` +
5956
+ `\n${awaitDeclaration}${doc}${useTypeScriptSyntax ? '' : 'export '}const ${componentName} = __sveltets_2_fn_component(${renderCall});\n` +
5891
5957
  // Surround the type with ignore comments so it is filtered out from go-to-definition etc,
5892
5958
  // which for some editors can cause duplicates
5893
- surroundWithIgnoreComments(`type ${componentName} = ReturnType<typeof ${componentName}>;\n`) +
5959
+ surroundWithIgnoreComments(useTypeScriptSyntax
5960
+ ? `type ${componentName} = ReturnType<typeof ${componentName}>;\n`
5961
+ : `/** @typedef {ReturnType<typeof ${componentName}>} ${componentName} */\n`) +
5894
5962
  `export default ${componentName};`;
5895
5963
  }
5896
5964
  else {
5897
5965
  statement =
5898
- `\n${awaitDeclaration}${doc}const ${componentName} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` +
5899
- surroundWithIgnoreComments(`type ${componentName} = InstanceType<typeof ${componentName}>;\n`) +
5966
+ `\n${awaitDeclaration}${doc}${useTypeScriptSyntax ? '' : 'export '}const ${componentName} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` +
5967
+ surroundWithIgnoreComments(useTypeScriptSyntax
5968
+ ? `type ${componentName} = InstanceType<typeof ${componentName}>;\n`
5969
+ : `/** @typedef {InstanceType<typeof ${componentName}>} ${componentName} */\n`) +
5900
5970
  `export default ${componentName};`;
5901
5971
  }
5902
5972
  }
@@ -5987,9 +6057,14 @@ function classNameFromFilename(filename, appendSuffix) {
5987
6057
  }
5988
6058
  }
5989
6059
 
5990
- function createRenderFunction({ str, scriptTag, scriptDestination, slots, events, exportedNames, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, hasTopLevelAwait, isTsFile, mode }) {
6060
+ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events, exportedNames, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, hasTopLevelAwait, isTsFile, mode, emitJsDoc = false }) {
5991
6061
  const htmlx = str.original;
5992
6062
  let propsDecl = '';
6063
+ const useTypescriptSyntax = isTsFile || !emitJsDoc;
6064
+ const templateNames = useTypescriptSyntax ? [] : generics.getReferences();
6065
+ const templateComment = !useTypescriptSyntax && templateNames.length
6066
+ ? `\n/** @template ${templateNames.join(', ')} */\n`
6067
+ : '';
5993
6068
  if (uses$$props) {
5994
6069
  propsDecl += ' let $$props = __sveltets_2_allPropsType();';
5995
6070
  }
@@ -6004,12 +6079,11 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6004
6079
  .join(', ') +
6005
6080
  '});';
6006
6081
  }
6007
- const slotsDeclaration = slots.size > 0 && mode !== 'dts'
6008
- ? '\n' +
6009
- surroundWithIgnoreComments(';const __sveltets_createSlot = __sveltets_2_createCreateSlot' +
6010
- (uses$$SlotsInterface ? '<$$Slots>' : '') +
6011
- '();')
6012
- : '';
6082
+ const slotTypeReference = uses$$SlotsInterface ? '<$$Slots>' : '';
6083
+ const slotDeclaration = useTypescriptSyntax || !slotTypeReference
6084
+ ? `;const __sveltets_createSlot = __sveltets_2_createCreateSlot${slotTypeReference}();`
6085
+ : `/** @type {ReturnType<typeof __sveltets_2_createCreateSlot${slotTypeReference}>} */ const __sveltets_createSlot = __sveltets_2_createCreateSlot();`;
6086
+ const slotsDeclaration = slots.size > 0 && mode !== 'dts' ? '\n' + surroundWithIgnoreComments(slotDeclaration) : '';
6013
6087
  if (scriptTag) {
6014
6088
  //I couldn't get magicstring to let me put the script before the <> we prepend during conversion of the template to jsx, so we just close it instead
6015
6089
  const scriptTagEnd = htmlx.lastIndexOf('>', scriptTag.content.start) + 1;
@@ -6021,12 +6095,19 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6021
6095
  start++;
6022
6096
  end--;
6023
6097
  }
6024
- str.overwrite(scriptTag.start + 1, start - 1, `${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}`);
6025
- str.overwrite(start - 1, start, isTsFile ? '<' : `<${IGNORE_START_COMMENT}`); // if the generics are unused, only this char is colored opaque
6026
- str.overwrite(end, scriptTagEnd, `>${isTsFile ? '' : IGNORE_END_COMMENT}() {${propsDecl}\n`);
6098
+ str.overwrite(scriptTag.start + 1, start - 1, `${templateComment}${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}`);
6099
+ if (useTypescriptSyntax) {
6100
+ str.overwrite(start - 1, start, isTsFile ? '<' : `<${IGNORE_START_COMMENT}`); // if the generics are unused, only this char is colored opaque
6101
+ str.overwrite(end, scriptTagEnd, `>${isTsFile ? '' : IGNORE_END_COMMENT}() {${propsDecl}\n`);
6102
+ }
6103
+ else {
6104
+ str.overwrite(start - 1, start, '');
6105
+ str.overwrite(start, end, '');
6106
+ str.overwrite(end, scriptTagEnd, `() {${propsDecl}\n`);
6107
+ }
6027
6108
  }
6028
6109
  else {
6029
- str.overwrite(scriptTag.start + 1, scriptTagEnd, `${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}${generics.toDefinitionString(true)}() {${propsDecl}\n`);
6110
+ str.overwrite(scriptTag.start + 1, scriptTagEnd, `${templateComment}${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}${useTypescriptSyntax ? generics.toDefinitionString(true) : ''}() {${propsDecl}\n`);
6030
6111
  }
6031
6112
  const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
6032
6113
  // wrap template with callback
@@ -6035,7 +6116,7 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6035
6116
  });
6036
6117
  }
6037
6118
  else {
6038
- str.prependRight(scriptDestination, `;${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}() {` +
6119
+ str.prependRight(scriptDestination, `;${templateComment}${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}${useTypescriptSyntax ? generics.toDefinitionString(true) : ''}() {` +
6039
6120
  `${propsDecl}${slotsDeclaration}\nasync () => {`);
6040
6121
  }
6041
6122
  const slotsAsDef = uses$$SlotsInterface
@@ -6316,6 +6397,11 @@ class HoistableInterfaces {
6316
6397
  }
6317
6398
  }
6318
6399
  }
6400
+ addDisallowed(names) {
6401
+ for (const name of names) {
6402
+ this.disallowed_values.add(name);
6403
+ }
6404
+ }
6319
6405
  /**
6320
6406
  * Traverses the AST to collect import statements and top-level interfaces,
6321
6407
  * then determines which interfaces can be hoisted.
@@ -6455,13 +6541,14 @@ function is$$PropsDeclaration(node) {
6455
6541
  return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Props';
6456
6542
  }
6457
6543
  class ExportedNames {
6458
- constructor(str, astOffset, basename, isTsFile, isSvelte5Plus, isRunes) {
6544
+ constructor(str, astOffset, basename, isTsFile, isSvelte5Plus, isRunes, emitJsDoc) {
6459
6545
  this.str = str;
6460
6546
  this.astOffset = astOffset;
6461
6547
  this.basename = basename;
6462
6548
  this.isTsFile = isTsFile;
6463
6549
  this.isSvelte5Plus = isSvelte5Plus;
6464
6550
  this.isRunes = isRunes;
6551
+ this.emitJsDoc = emitJsDoc;
6465
6552
  this.hoistableInterfaces = new HoistableInterfaces();
6466
6553
  this.usesAccessors = false;
6467
6554
  /**
@@ -6489,6 +6576,22 @@ class ExportedNames {
6489
6576
  this.doneDeclarationTransformation = new Set();
6490
6577
  this.getters = new Set();
6491
6578
  }
6579
+ /**
6580
+ * Emits a Kit type annotation (e.g. `: import('./$types.js').PageData`) either as a
6581
+ * JSDoc @type comment (for JS files in emitJsDoc mode) or as a TS type annotation
6582
+ * wrapped in ignore comments.
6583
+ */
6584
+ emitKitType(kitType, nameStart, nameEnd) {
6585
+ if (!kitType)
6586
+ return;
6587
+ if (this.emitJsDoc && !this.isTsFile) {
6588
+ const kitTypeRef = kitType.slice(2); // strip leading ": "
6589
+ preprendStr(this.str, nameStart, `/** @type {${kitTypeRef}} */ `);
6590
+ }
6591
+ else {
6592
+ preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6593
+ }
6594
+ }
6492
6595
  handleVariableStatement(node, parent) {
6493
6596
  const exportModifier = findExportKeyword(node);
6494
6597
  if (exportModifier) {
@@ -6507,10 +6610,7 @@ class ExportedNames {
6507
6610
  n.name.getText() === 'snapshot';
6508
6611
  // TS types are not allowed in JS files, but TS will still pick it up and the ignore comment will filter out the error
6509
6612
  const kitType = isKitExport && !type ? `: import('./$types.js').Snapshot` : '';
6510
- const nameEnd = n.name.end + this.astOffset;
6511
- if (kitType) {
6512
- preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6513
- }
6613
+ this.emitKitType(kitType, n.name.getStart() + this.astOffset, n.name.end + this.astOffset);
6514
6614
  }
6515
6615
  });
6516
6616
  }
@@ -6794,17 +6894,20 @@ class ExportedNames {
6794
6894
  [ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.TrueKeyword].includes(declaration.initializer.kind)))) {
6795
6895
  const name = identifier.getText();
6796
6896
  if (nameEnd === end) {
6797
- preprendStr(this.str, end, surroundWithIgnoreComments(`${kitType};${name} = __sveltets_2_any(${name});`));
6897
+ if (kitType && this.emitJsDoc && !this.isTsFile) {
6898
+ const kitTypeRef = kitType.slice(2);
6899
+ const nameStart = identifier.getStart() + this.astOffset;
6900
+ preprendStr(this.str, nameStart, `/** @type {${kitTypeRef}} */ `);
6901
+ }
6902
+ preprendStr(this.str, end, surroundWithIgnoreComments(`${(this.isTsFile || !this.emitJsDoc) && kitType ? kitType : ''};${name} = __sveltets_2_any(${name});`));
6798
6903
  }
6799
6904
  else {
6800
- if (kitType) {
6801
- preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6802
- }
6905
+ this.emitKitType(kitType, identifier.getStart() + this.astOffset, nameEnd);
6803
6906
  preprendStr(this.str, end, surroundWithIgnoreComments(`;${name} = __sveltets_2_any(${name});`));
6804
6907
  }
6805
6908
  }
6806
- else if (kitType) {
6807
- preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6909
+ else {
6910
+ this.emitKitType(kitType, identifier.getStart() + this.astOffset, nameEnd);
6808
6911
  }
6809
6912
  };
6810
6913
  const findComma = (target) => target.getChildren().filter((child) => child.kind === ts.SyntaxKind.CommaToken);
@@ -7583,12 +7686,12 @@ class InterfacesAndTypes {
7583
7686
  }
7584
7687
  }
7585
7688
 
7586
- function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, moduleAst, isTSFile, basename, isSvelte5Plus, isRunes) {
7689
+ function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, moduleAst, isTSFile, basename, isSvelte5Plus, isRunes, emitJsDoc) {
7587
7690
  const htmlx = str.original;
7588
7691
  const scriptContent = htmlx.substring(script.content.start, script.content.end);
7589
7692
  const tsAst = ts.createSourceFile('component.ts.svelte', scriptContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
7590
7693
  const astOffset = script.content.start;
7591
- const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile, isSvelte5Plus, isRunes);
7694
+ const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile, isSvelte5Plus, isRunes, emitJsDoc);
7592
7695
  const generics = new Generics(str, astOffset, script);
7593
7696
  const interfacesAndTypes = new InterfacesAndTypes();
7594
7697
  if (moduleAst) {
@@ -7821,6 +7924,7 @@ function processInstanceScriptContent(str, script, events, implicitStoreValues,
7821
7924
  for (const node of nodesToMove) {
7822
7925
  moveNode(node, str, astOffset, script.start, tsAst);
7823
7926
  }
7927
+ exportedNames.hoistableInterfaces.addDisallowed(implicitStoreValues.getAccessedStores());
7824
7928
  const hoisted = exportedNames.hoistableInterfaces.moveHoistableInterfaces(str, astOffset, script.start + 1, // +1 because imports are also moved at that position, and we want to move interfaces after imports
7825
7929
  generics.getReferences());
7826
7930
  if (mode === 'dts') {
@@ -7944,12 +8048,14 @@ function processSvelteTemplate(str, parse, options) {
7944
8048
  return convertHtmlxToJsx(str, htmlxAst, tags, options);
7945
8049
  }
7946
8050
  function svelte2tsx(svelte, options = { parse: compiler.parse }) {
8051
+ var _a;
7947
8052
  options.mode = options.mode || 'ts';
7948
8053
  options.version = options.version || compiler.VERSION;
7949
8054
  const str = new MagicString(svelte);
7950
8055
  const basename = path.basename(options.filename || '');
7951
8056
  const svelte5Plus = Number(options.version[0]) > 4;
7952
8057
  const isTsFile = options === null || options === void 0 ? void 0 : options.isTsFile;
8058
+ const emitJsDoc = (_a = options === null || options === void 0 ? void 0 : options.emitJsDoc) !== null && _a !== void 0 ? _a : false;
7953
8059
  // process the htmlx as a svelte template
7954
8060
  let { htmlAst, moduleScriptTag, scriptTag, rootSnippets, slots, uses$$props, uses$$slots, uses$$restProps, events, componentDocumentation, resolvedStores, usesAccessors, isRunes } = processSvelteTemplate(str, options.parse || compiler.parse, {
7955
8061
  ...options,
@@ -7978,7 +8084,7 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
7978
8084
  : instanceScriptTarget;
7979
8085
  const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart, svelte5Plus);
7980
8086
  //move the instance script and process the content
7981
- let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes);
8087
+ let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes, emitJsDoc);
7982
8088
  let generics = new Generics(str, 0, { attributes: [] });
7983
8089
  let uses$$SlotsInterface = false;
7984
8090
  let hasTopLevelAwait = false;
@@ -7987,7 +8093,7 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
7987
8093
  if (scriptTag.start != instanceScriptTarget) {
7988
8094
  str.move(scriptTag.start, scriptTag.end, instanceScriptTarget);
7989
8095
  }
7990
- const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues, options.mode, moduleAst, isTsFile, basename, svelte5Plus, isRunes);
8096
+ const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues, options.mode, moduleAst, isTsFile, basename, svelte5Plus, isRunes, emitJsDoc);
7991
8097
  uses$$props = uses$$props || res.uses$$props;
7992
8098
  uses$$restProps = uses$$restProps || res.uses$$restProps;
7993
8099
  uses$$slots = uses$$slots || res.uses$$slots;
@@ -8016,7 +8122,8 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
8016
8122
  hasTopLevelAwait,
8017
8123
  svelte5Plus,
8018
8124
  isTsFile,
8019
- mode: options.mode
8125
+ mode: options.mode,
8126
+ emitJsDoc
8020
8127
  });
8021
8128
  // we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items
8022
8129
  if (moduleScriptTag) {
@@ -8069,7 +8176,8 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
8069
8176
  generics,
8070
8177
  isSvelte5: svelte5Plus,
8071
8178
  hasTopLevelAwait,
8072
- noSvelteComponentTyped: options.noSvelteComponentTyped
8179
+ noSvelteComponentTyped: options.noSvelteComponentTyped,
8180
+ emitJsDoc
8073
8181
  });
8074
8182
  if (options.mode === 'dts') {
8075
8183
  // Prepend the import which is used for TS files
package/index.mjs CHANGED
@@ -2819,7 +2819,7 @@ const supportsBindThis = [
2819
2819
  /**
2820
2820
  * Transform bind:xxx into something that conforms to JS/TS
2821
2821
  */
2822
- function handleBinding(str, attr, parent, element, preserveBind, isSvelte5Plus) {
2822
+ function handleBinding(str, attr, parent, element, preserveBind, isSvelte5Plus, emitJsDoc, isTsFile) {
2823
2823
  const isGetSetBinding = attr.expression.type === 'SequenceExpression';
2824
2824
  const [get, set] = isGetSetBinding ? attr.expression.expressions : [];
2825
2825
  // bind this
@@ -2851,9 +2851,14 @@ function handleBinding(str, attr, parent, element, preserveBind, isSvelte5Plus)
2851
2851
  }
2852
2852
  // one way binding whose property is not on the element
2853
2853
  if (oneWayBindingAttributesNotOnElement.has(attr.name) && element instanceof Element) {
2854
+ const useTypescriptSyntax = isTsFile || !emitJsDoc;
2855
+ const bindingType = oneWayBindingAttributesNotOnElement.get(attr.name);
2856
+ const bindingValue = useTypescriptSyntax
2857
+ ? `null as ${bindingType}`
2858
+ : `/** @type {${bindingType}} */ (null)`;
2854
2859
  element.appendToStartEnd([
2855
2860
  [attr.expression.start, getEnd(attr.expression)],
2856
- `= ${surroundWithIgnoreComments(`null as ${oneWayBindingAttributesNotOnElement.get(attr.name)}`)};`
2861
+ `= ${surroundWithIgnoreComments(bindingValue)};`
2857
2862
  ]);
2858
2863
  return;
2859
2864
  }
@@ -3318,10 +3323,11 @@ function handleTransitionDirective(str, attr, element) {
3318
3323
  * }
3319
3324
  * ```
3320
3325
  */
3321
- function handleSnippet(str, snippetBlock, component) {
3326
+ function handleSnippet(str, snippetBlock, component, emitJsDoc = false, isTsFile = false) {
3322
3327
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3323
3328
  const isImplicitProp = component !== undefined;
3324
3329
  const endSnippet = str.original.lastIndexOf('{', snippetBlock.end - 1);
3330
+ const usTSSyntax = isTsFile || !emitJsDoc;
3325
3331
  const afterSnippet = isImplicitProp
3326
3332
  ? `};return __sveltets_2_any(0)}`
3327
3333
  : `};return __sveltets_2_any(0)};`;
@@ -3387,13 +3393,14 @@ function handleSnippet(str, snippetBlock, component) {
3387
3393
  'const ',
3388
3394
  [snippetBlock.expression.start, snippetBlock.expression.end],
3389
3395
  IGNORE_POSITION_COMMENT,
3390
- ` = ${snippetBlock.typeParams ? `<${snippetBlock.typeParams}>` : ''}(`
3396
+ ` = ${usTSSyntax ? '' : "/** @returns {ReturnType<import('svelte').Snippet>} */ "}${usTSSyntax && snippetBlock.typeParams ? `<${snippetBlock.typeParams}>` : ''}(`
3391
3397
  ];
3392
3398
  if (parameters) {
3393
3399
  transforms.push(parameters);
3394
3400
  }
3395
- transforms.push(')', surroundWithIgnoreComments(`: ReturnType<import('svelte').Snippet>`), // shows up nicely preserved on hover, other alternatives don't
3396
- afterParameters);
3401
+ transforms.push(')', usTSSyntax
3402
+ ? surroundWithIgnoreComments(`: ReturnType<import('svelte').Snippet>`) // shows up nicely preserved on hover, other alternatives don't
3403
+ : '', afterParameters);
3397
3404
  transform(str, snippetBlock.start, startEnd, transforms);
3398
3405
  }
3399
3406
  }
@@ -4875,8 +4882,11 @@ function handleAttachTag(tag, element) {
4875
4882
  * and converts it to JSX
4876
4883
  */
4877
4884
  function convertHtmlxToJsx(str, ast, tags, options = { svelte5Plus: false }) {
4885
+ var _a, _b;
4878
4886
  options.typingsNamespace = options.typingsNamespace || 'svelteHTML';
4879
4887
  const preserveAttributeCase = options.namespace === 'foreign';
4888
+ const emitJsDoc = (_a = options.emitJsDoc) !== null && _a !== void 0 ? _a : false;
4889
+ const isTsFile = (_b = options.isTsFile) !== null && _b !== void 0 ? _b : false;
4880
4890
  const rootSnippets = [];
4881
4891
  let element;
4882
4892
  const pendingSnippetHoistCheck = new Set();
@@ -5012,7 +5022,7 @@ function convertHtmlxToJsx(str, ast, tags, options = { svelte5Plus: false }) {
5012
5022
  (element instanceof Element &&
5013
5023
  element.tagName === 'svelte:boundary')
5014
5024
  ? element
5015
- : undefined);
5025
+ : undefined, emitJsDoc, isTsFile);
5016
5026
  if (parent === ast) {
5017
5027
  // root snippet -> move to instance script or possibly even module script
5018
5028
  const result = analyze({
@@ -5108,7 +5118,7 @@ function convertHtmlxToJsx(str, ast, tags, options = { svelte5Plus: false }) {
5108
5118
  handleComment(str, node);
5109
5119
  break;
5110
5120
  case 'Binding':
5111
- handleBinding(str, node, parent, element, options.typingsNamespace === 'svelteHTML', options.svelte5Plus);
5121
+ handleBinding(str, node, parent, element, options.typingsNamespace === 'svelteHTML', options.svelte5Plus, emitJsDoc, isTsFile);
5112
5122
  break;
5113
5123
  case 'Class':
5114
5124
  handleClassDirective(str, node, element);
@@ -5691,21 +5701,33 @@ function addComponentExport(params) {
5691
5701
  addSimpleComponentExport(params);
5692
5702
  }
5693
5703
  }
5694
- function addGenericsComponentExport({ events, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, generics, usesSlots, isSvelte5, noSvelteComponentTyped, hasTopLevelAwait }) {
5704
+ function addGenericsComponentExport({ events, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, isTsFile, generics, usesSlots, isSvelte5, noSvelteComponentTyped, hasTopLevelAwait, emitJsDoc }) {
5695
5705
  const genericsDef = generics.toDefinitionString();
5696
5706
  const genericsRef = generics.toReferencesString();
5707
+ const useTSSyntax = isTsFile || !emitJsDoc;
5708
+ const templateNames = !useTSSyntax && genericsDef
5709
+ ? genericsDef
5710
+ .slice(1, -1)
5711
+ .split(',')
5712
+ .map((part) => part.trim())
5713
+ .map((part) => part.split(/\s|=/)[0])
5714
+ .filter(Boolean)
5715
+ : [];
5716
+ const templateComment = templateNames.length
5717
+ ? `/** @template ${templateNames.join(', ')} */\n`
5718
+ : '';
5697
5719
  const doc = componentDocumentation.getFormatted();
5698
5720
  const className = fileName && classNameFromFilename(fileName, mode !== 'dts');
5699
5721
  function returnType(forPart) {
5700
5722
  return `ReturnType<__sveltets_Render${genericsRef}['${forPart}']>`;
5701
5723
  }
5702
5724
  const renderCall = hasTopLevelAwait
5703
- ? `(await ${internalHelpers.renderName}${genericsRef}())`
5704
- : `${internalHelpers.renderName}${genericsRef}()`;
5725
+ ? `(await ${internalHelpers.renderName}${useTSSyntax ? genericsRef : ''}())`
5726
+ : `${internalHelpers.renderName}${useTSSyntax ? genericsRef : ''}()`;
5705
5727
  // TODO once Svelte 4 compatibility is dropped, we can simplify this, because since TS 4.7 it is possible to use generics
5706
5728
  // like this: `typeof render<T>` - which wasn't possibly before, hence the class + methods workaround.
5707
5729
  let statement = `
5708
- class __sveltets_Render${genericsDef} {
5730
+ ${templateComment}class __sveltets_Render${useTSSyntax ? genericsDef : ''} {
5709
5731
  props() {
5710
5732
  return ${props(true, canHaveAnyProp, exportedNames, renderCall)}.props;
5711
5733
  }
@@ -5721,12 +5743,25 @@ class __sveltets_Render${genericsDef} {
5721
5743
  const renderType = hasTopLevelAwait
5722
5744
  ? `Awaited<ReturnType<typeof ${internalHelpers.renderName}${genericsRef}>>`
5723
5745
  : `ReturnType<typeof ${internalHelpers.renderName}${genericsRef}>`;
5724
- statement = `
5746
+ if (useTSSyntax) {
5747
+ statement = `
5725
5748
  class __sveltets_Render${genericsDef} {
5726
5749
  props(): ${renderType}['props'] { return null as any; }
5727
5750
  events(): ${renderType}['events'] { return null as any; }
5728
5751
  slots(): ${renderType}['slots'] { return null as any; }
5729
5752
  `;
5753
+ }
5754
+ else {
5755
+ statement = `
5756
+ ${templateComment}class __sveltets_Render {
5757
+ /** @returns {${renderType}['props']} */
5758
+ props() { return /** @type {any} */ (null); }
5759
+ /** @returns {${renderType}['events']} */
5760
+ events() { return /** @type {any} */ (null); }
5761
+ /** @returns {${renderType}['slots']} */
5762
+ slots() { return /** @type {any} */ (null); }
5763
+ `;
5764
+ }
5730
5765
  }
5731
5766
  statement += isSvelte5
5732
5767
  ? ` bindings() { return ${exportedNames.createBindingsStr()}; }
@@ -5758,15 +5793,29 @@ class __sveltets_Render${genericsDef} {
5758
5793
  // - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes
5759
5794
  // - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc)
5760
5795
  // TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then.
5761
- statement +=
5762
- `\ninterface $$IsomorphicComponent {\n` +
5763
- ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` +
5764
- ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` +
5765
- ` z_$$bindings?: ${bindingsType};\n` +
5766
- `}\n` +
5767
- `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` +
5768
- surroundWithIgnoreComments(`type ${className || '$$Component'}${genericsDef} = InstanceType<typeof ${className || '$$Component'}${genericsRef}>;\n`) +
5769
- `export default ${className || '$$Component'};`;
5796
+ if (useTSSyntax) {
5797
+ statement +=
5798
+ `\ninterface $$IsomorphicComponent {\n` +
5799
+ ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` +
5800
+ ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` +
5801
+ ` z_$$bindings?: ${bindingsType};\n` +
5802
+ `}\n` +
5803
+ `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` +
5804
+ surroundWithIgnoreComments(`type ${className || '$$Component'}${genericsDef} = InstanceType<typeof ${className || '$$Component'}${genericsRef}>;\n`) +
5805
+ `export default ${className || '$$Component'};`;
5806
+ }
5807
+ else {
5808
+ const componentName = className || '$$Component';
5809
+ const componentType = `(new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>) => import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')}) & ` +
5810
+ `(${genericsDef}(internal: unknown, props: ${propsType}) => ${returnType('exports')}) & ` +
5811
+ `{z_$$bindings?: ${bindingsType}}`;
5812
+ statement +=
5813
+ `\n${templateComment}` +
5814
+ `/** @typedef {${componentType}} $$IsomorphicComponent */\n` +
5815
+ `${doc}/** @type {$$IsomorphicComponent} */ export const ${componentName} = /** @type {any} */(null);\n` +
5816
+ `/** @typedef {InstanceType<typeof ${componentName}>} ${componentName} */\n` +
5817
+ `export default ${componentName};`;
5818
+ }
5770
5819
  }
5771
5820
  else if (mode === 'dts') {
5772
5821
  statement +=
@@ -5779,16 +5828,30 @@ class __sveltets_Render${genericsDef} {
5779
5828
  '\n}';
5780
5829
  }
5781
5830
  else {
5782
- statement +=
5783
- `\n\nimport { ${svelteComponentClass} as __SvelteComponentTyped__ } from "svelte" \n` +
5784
- `${doc}export default class${className ? ` ${className}` : ''}${genericsDef} extends __SvelteComponentTyped__<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> {` +
5785
- exportedNames.createClassGetters(genericsRef) +
5786
- (usesAccessors ? exportedNames.createClassAccessors() : '') +
5787
- '\n}';
5831
+ if (useTSSyntax) {
5832
+ statement +=
5833
+ `\n\nimport { ${svelteComponentClass} as __SvelteComponentTyped__ } from "svelte" \n` +
5834
+ `${doc}export default class${className ? ` ${className}` : ''}${genericsDef} extends __SvelteComponentTyped__<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> {` +
5835
+ exportedNames.createClassGetters(genericsRef) +
5836
+ (usesAccessors ? exportedNames.createClassAccessors() : '') +
5837
+ '\n}';
5838
+ }
5839
+ else {
5840
+ statement +=
5841
+ `\n\nimport { ${svelteComponentClass} as __SvelteComponentTyped__ } from "svelte" \n` +
5842
+ `/**` +
5843
+ templateNames.map((t) => ` * @template ${t}\n`).join('') +
5844
+ ` * @extends {__SvelteComponentTyped__<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}>}\n` +
5845
+ ` */\n` +
5846
+ `export default class${className ? ` ${className}` : ''} extends __SvelteComponentTyped__ {` +
5847
+ exportedNames.createClassGetters(genericsRef) +
5848
+ (usesAccessors ? exportedNames.createClassAccessors() : '') +
5849
+ '\n}';
5850
+ }
5788
5851
  }
5789
5852
  str.append(statement);
5790
5853
  }
5791
- function addSimpleComponentExport({ events, isTsFile, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, usesSlots, noSvelteComponentTyped, isSvelte5, hasTopLevelAwait }) {
5854
+ function addSimpleComponentExport({ events, isTsFile, canHaveAnyProp, exportedNames, componentDocumentation, fileName, mode, usesAccessors, str, usesSlots, noSvelteComponentTyped, isSvelte5, hasTopLevelAwait, emitJsDoc }) {
5792
5855
  const renderCall = hasTopLevelAwait
5793
5856
  ? `$${internalHelpers.renderName}`
5794
5857
  : `${internalHelpers.renderName}()`;
@@ -5865,18 +5928,25 @@ declare function $$__sveltets_2_isomorphic_component<
5865
5928
  }
5866
5929
  else {
5867
5930
  if (isSvelte5) {
5931
+ // When emitting JSDoc (JS files in incremental mode), we use @typedef instead of
5932
+ // TS type syntax, and must export the const so declaration-merging with @typedef works.
5933
+ const useTypeScriptSyntax = isTsFile || !emitJsDoc;
5868
5934
  if (exportedNames.isRunesMode() && !usesSlots && !events.hasEvents()) {
5869
5935
  statement =
5870
- `\n${awaitDeclaration}${doc}const ${componentName} = __sveltets_2_fn_component(${renderCall});\n` +
5936
+ `\n${awaitDeclaration}${doc}${useTypeScriptSyntax ? '' : 'export '}const ${componentName} = __sveltets_2_fn_component(${renderCall});\n` +
5871
5937
  // Surround the type with ignore comments so it is filtered out from go-to-definition etc,
5872
5938
  // which for some editors can cause duplicates
5873
- surroundWithIgnoreComments(`type ${componentName} = ReturnType<typeof ${componentName}>;\n`) +
5939
+ surroundWithIgnoreComments(useTypeScriptSyntax
5940
+ ? `type ${componentName} = ReturnType<typeof ${componentName}>;\n`
5941
+ : `/** @typedef {ReturnType<typeof ${componentName}>} ${componentName} */\n`) +
5874
5942
  `export default ${componentName};`;
5875
5943
  }
5876
5944
  else {
5877
5945
  statement =
5878
- `\n${awaitDeclaration}${doc}const ${componentName} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` +
5879
- surroundWithIgnoreComments(`type ${componentName} = InstanceType<typeof ${componentName}>;\n`) +
5946
+ `\n${awaitDeclaration}${doc}${useTypeScriptSyntax ? '' : 'export '}const ${componentName} = __sveltets_2_isomorphic_component${usesSlots ? '_slots' : ''}(${propDef});\n` +
5947
+ surroundWithIgnoreComments(useTypeScriptSyntax
5948
+ ? `type ${componentName} = InstanceType<typeof ${componentName}>;\n`
5949
+ : `/** @typedef {InstanceType<typeof ${componentName}>} ${componentName} */\n`) +
5880
5950
  `export default ${componentName};`;
5881
5951
  }
5882
5952
  }
@@ -5967,9 +6037,14 @@ function classNameFromFilename(filename, appendSuffix) {
5967
6037
  }
5968
6038
  }
5969
6039
 
5970
- function createRenderFunction({ str, scriptTag, scriptDestination, slots, events, exportedNames, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, hasTopLevelAwait, isTsFile, mode }) {
6040
+ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events, exportedNames, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, hasTopLevelAwait, isTsFile, mode, emitJsDoc = false }) {
5971
6041
  const htmlx = str.original;
5972
6042
  let propsDecl = '';
6043
+ const useTypescriptSyntax = isTsFile || !emitJsDoc;
6044
+ const templateNames = useTypescriptSyntax ? [] : generics.getReferences();
6045
+ const templateComment = !useTypescriptSyntax && templateNames.length
6046
+ ? `\n/** @template ${templateNames.join(', ')} */\n`
6047
+ : '';
5973
6048
  if (uses$$props) {
5974
6049
  propsDecl += ' let $$props = __sveltets_2_allPropsType();';
5975
6050
  }
@@ -5984,12 +6059,11 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
5984
6059
  .join(', ') +
5985
6060
  '});';
5986
6061
  }
5987
- const slotsDeclaration = slots.size > 0 && mode !== 'dts'
5988
- ? '\n' +
5989
- surroundWithIgnoreComments(';const __sveltets_createSlot = __sveltets_2_createCreateSlot' +
5990
- (uses$$SlotsInterface ? '<$$Slots>' : '') +
5991
- '();')
5992
- : '';
6062
+ const slotTypeReference = uses$$SlotsInterface ? '<$$Slots>' : '';
6063
+ const slotDeclaration = useTypescriptSyntax || !slotTypeReference
6064
+ ? `;const __sveltets_createSlot = __sveltets_2_createCreateSlot${slotTypeReference}();`
6065
+ : `/** @type {ReturnType<typeof __sveltets_2_createCreateSlot${slotTypeReference}>} */ const __sveltets_createSlot = __sveltets_2_createCreateSlot();`;
6066
+ const slotsDeclaration = slots.size > 0 && mode !== 'dts' ? '\n' + surroundWithIgnoreComments(slotDeclaration) : '';
5993
6067
  if (scriptTag) {
5994
6068
  //I couldn't get magicstring to let me put the script before the <> we prepend during conversion of the template to jsx, so we just close it instead
5995
6069
  const scriptTagEnd = htmlx.lastIndexOf('>', scriptTag.content.start) + 1;
@@ -6001,12 +6075,19 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6001
6075
  start++;
6002
6076
  end--;
6003
6077
  }
6004
- str.overwrite(scriptTag.start + 1, start - 1, `${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}`);
6005
- str.overwrite(start - 1, start, isTsFile ? '<' : `<${IGNORE_START_COMMENT}`); // if the generics are unused, only this char is colored opaque
6006
- str.overwrite(end, scriptTagEnd, `>${isTsFile ? '' : IGNORE_END_COMMENT}() {${propsDecl}\n`);
6078
+ str.overwrite(scriptTag.start + 1, start - 1, `${templateComment}${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}`);
6079
+ if (useTypescriptSyntax) {
6080
+ str.overwrite(start - 1, start, isTsFile ? '<' : `<${IGNORE_START_COMMENT}`); // if the generics are unused, only this char is colored opaque
6081
+ str.overwrite(end, scriptTagEnd, `>${isTsFile ? '' : IGNORE_END_COMMENT}() {${propsDecl}\n`);
6082
+ }
6083
+ else {
6084
+ str.overwrite(start - 1, start, '');
6085
+ str.overwrite(start, end, '');
6086
+ str.overwrite(end, scriptTagEnd, `() {${propsDecl}\n`);
6087
+ }
6007
6088
  }
6008
6089
  else {
6009
- str.overwrite(scriptTag.start + 1, scriptTagEnd, `${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}${generics.toDefinitionString(true)}() {${propsDecl}\n`);
6090
+ str.overwrite(scriptTag.start + 1, scriptTagEnd, `${templateComment}${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}${useTypescriptSyntax ? generics.toDefinitionString(true) : ''}() {${propsDecl}\n`);
6010
6091
  }
6011
6092
  const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
6012
6093
  // wrap template with callback
@@ -6015,7 +6096,7 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6015
6096
  });
6016
6097
  }
6017
6098
  else {
6018
- str.prependRight(scriptDestination, `;${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}() {` +
6099
+ str.prependRight(scriptDestination, `;${templateComment}${hasTopLevelAwait ? 'async ' : ''}function ${internalHelpers.renderName}${useTypescriptSyntax ? generics.toDefinitionString(true) : ''}() {` +
6019
6100
  `${propsDecl}${slotsDeclaration}\nasync () => {`);
6020
6101
  }
6021
6102
  const slotsAsDef = uses$$SlotsInterface
@@ -6296,6 +6377,11 @@ class HoistableInterfaces {
6296
6377
  }
6297
6378
  }
6298
6379
  }
6380
+ addDisallowed(names) {
6381
+ for (const name of names) {
6382
+ this.disallowed_values.add(name);
6383
+ }
6384
+ }
6299
6385
  /**
6300
6386
  * Traverses the AST to collect import statements and top-level interfaces,
6301
6387
  * then determines which interfaces can be hoisted.
@@ -6435,13 +6521,14 @@ function is$$PropsDeclaration(node) {
6435
6521
  return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Props';
6436
6522
  }
6437
6523
  class ExportedNames {
6438
- constructor(str, astOffset, basename, isTsFile, isSvelte5Plus, isRunes) {
6524
+ constructor(str, astOffset, basename, isTsFile, isSvelte5Plus, isRunes, emitJsDoc) {
6439
6525
  this.str = str;
6440
6526
  this.astOffset = astOffset;
6441
6527
  this.basename = basename;
6442
6528
  this.isTsFile = isTsFile;
6443
6529
  this.isSvelte5Plus = isSvelte5Plus;
6444
6530
  this.isRunes = isRunes;
6531
+ this.emitJsDoc = emitJsDoc;
6445
6532
  this.hoistableInterfaces = new HoistableInterfaces();
6446
6533
  this.usesAccessors = false;
6447
6534
  /**
@@ -6469,6 +6556,22 @@ class ExportedNames {
6469
6556
  this.doneDeclarationTransformation = new Set();
6470
6557
  this.getters = new Set();
6471
6558
  }
6559
+ /**
6560
+ * Emits a Kit type annotation (e.g. `: import('./$types.js').PageData`) either as a
6561
+ * JSDoc @type comment (for JS files in emitJsDoc mode) or as a TS type annotation
6562
+ * wrapped in ignore comments.
6563
+ */
6564
+ emitKitType(kitType, nameStart, nameEnd) {
6565
+ if (!kitType)
6566
+ return;
6567
+ if (this.emitJsDoc && !this.isTsFile) {
6568
+ const kitTypeRef = kitType.slice(2); // strip leading ": "
6569
+ preprendStr(this.str, nameStart, `/** @type {${kitTypeRef}} */ `);
6570
+ }
6571
+ else {
6572
+ preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6573
+ }
6574
+ }
6472
6575
  handleVariableStatement(node, parent) {
6473
6576
  const exportModifier = findExportKeyword(node);
6474
6577
  if (exportModifier) {
@@ -6487,10 +6590,7 @@ class ExportedNames {
6487
6590
  n.name.getText() === 'snapshot';
6488
6591
  // TS types are not allowed in JS files, but TS will still pick it up and the ignore comment will filter out the error
6489
6592
  const kitType = isKitExport && !type ? `: import('./$types.js').Snapshot` : '';
6490
- const nameEnd = n.name.end + this.astOffset;
6491
- if (kitType) {
6492
- preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6493
- }
6593
+ this.emitKitType(kitType, n.name.getStart() + this.astOffset, n.name.end + this.astOffset);
6494
6594
  }
6495
6595
  });
6496
6596
  }
@@ -6774,17 +6874,20 @@ class ExportedNames {
6774
6874
  [ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.TrueKeyword].includes(declaration.initializer.kind)))) {
6775
6875
  const name = identifier.getText();
6776
6876
  if (nameEnd === end) {
6777
- preprendStr(this.str, end, surroundWithIgnoreComments(`${kitType};${name} = __sveltets_2_any(${name});`));
6877
+ if (kitType && this.emitJsDoc && !this.isTsFile) {
6878
+ const kitTypeRef = kitType.slice(2);
6879
+ const nameStart = identifier.getStart() + this.astOffset;
6880
+ preprendStr(this.str, nameStart, `/** @type {${kitTypeRef}} */ `);
6881
+ }
6882
+ preprendStr(this.str, end, surroundWithIgnoreComments(`${(this.isTsFile || !this.emitJsDoc) && kitType ? kitType : ''};${name} = __sveltets_2_any(${name});`));
6778
6883
  }
6779
6884
  else {
6780
- if (kitType) {
6781
- preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6782
- }
6885
+ this.emitKitType(kitType, identifier.getStart() + this.astOffset, nameEnd);
6783
6886
  preprendStr(this.str, end, surroundWithIgnoreComments(`;${name} = __sveltets_2_any(${name});`));
6784
6887
  }
6785
6888
  }
6786
- else if (kitType) {
6787
- preprendStr(this.str, nameEnd, surroundWithIgnoreComments(kitType));
6889
+ else {
6890
+ this.emitKitType(kitType, identifier.getStart() + this.astOffset, nameEnd);
6788
6891
  }
6789
6892
  };
6790
6893
  const findComma = (target) => target.getChildren().filter((child) => child.kind === ts.SyntaxKind.CommaToken);
@@ -7563,12 +7666,12 @@ class InterfacesAndTypes {
7563
7666
  }
7564
7667
  }
7565
7668
 
7566
- function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, moduleAst, isTSFile, basename, isSvelte5Plus, isRunes) {
7669
+ function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, moduleAst, isTSFile, basename, isSvelte5Plus, isRunes, emitJsDoc) {
7567
7670
  const htmlx = str.original;
7568
7671
  const scriptContent = htmlx.substring(script.content.start, script.content.end);
7569
7672
  const tsAst = ts.createSourceFile('component.ts.svelte', scriptContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
7570
7673
  const astOffset = script.content.start;
7571
- const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile, isSvelte5Plus, isRunes);
7674
+ const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile, isSvelte5Plus, isRunes, emitJsDoc);
7572
7675
  const generics = new Generics(str, astOffset, script);
7573
7676
  const interfacesAndTypes = new InterfacesAndTypes();
7574
7677
  if (moduleAst) {
@@ -7801,6 +7904,7 @@ function processInstanceScriptContent(str, script, events, implicitStoreValues,
7801
7904
  for (const node of nodesToMove) {
7802
7905
  moveNode(node, str, astOffset, script.start, tsAst);
7803
7906
  }
7907
+ exportedNames.hoistableInterfaces.addDisallowed(implicitStoreValues.getAccessedStores());
7804
7908
  const hoisted = exportedNames.hoistableInterfaces.moveHoistableInterfaces(str, astOffset, script.start + 1, // +1 because imports are also moved at that position, and we want to move interfaces after imports
7805
7909
  generics.getReferences());
7806
7910
  if (mode === 'dts') {
@@ -7924,12 +8028,14 @@ function processSvelteTemplate(str, parse, options) {
7924
8028
  return convertHtmlxToJsx(str, htmlxAst, tags, options);
7925
8029
  }
7926
8030
  function svelte2tsx(svelte, options = { parse }) {
8031
+ var _a;
7927
8032
  options.mode = options.mode || 'ts';
7928
8033
  options.version = options.version || VERSION;
7929
8034
  const str = new MagicString(svelte);
7930
8035
  const basename = path__default.basename(options.filename || '');
7931
8036
  const svelte5Plus = Number(options.version[0]) > 4;
7932
8037
  const isTsFile = options === null || options === void 0 ? void 0 : options.isTsFile;
8038
+ const emitJsDoc = (_a = options === null || options === void 0 ? void 0 : options.emitJsDoc) !== null && _a !== void 0 ? _a : false;
7933
8039
  // process the htmlx as a svelte template
7934
8040
  let { htmlAst, moduleScriptTag, scriptTag, rootSnippets, slots, uses$$props, uses$$slots, uses$$restProps, events, componentDocumentation, resolvedStores, usesAccessors, isRunes } = processSvelteTemplate(str, options.parse || parse, {
7935
8041
  ...options,
@@ -7958,7 +8064,7 @@ function svelte2tsx(svelte, options = { parse }) {
7958
8064
  : instanceScriptTarget;
7959
8065
  const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart, svelte5Plus);
7960
8066
  //move the instance script and process the content
7961
- let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes);
8067
+ let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes, emitJsDoc);
7962
8068
  let generics = new Generics(str, 0, { attributes: [] });
7963
8069
  let uses$$SlotsInterface = false;
7964
8070
  let hasTopLevelAwait = false;
@@ -7967,7 +8073,7 @@ function svelte2tsx(svelte, options = { parse }) {
7967
8073
  if (scriptTag.start != instanceScriptTarget) {
7968
8074
  str.move(scriptTag.start, scriptTag.end, instanceScriptTarget);
7969
8075
  }
7970
- const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues, options.mode, moduleAst, isTsFile, basename, svelte5Plus, isRunes);
8076
+ const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues, options.mode, moduleAst, isTsFile, basename, svelte5Plus, isRunes, emitJsDoc);
7971
8077
  uses$$props = uses$$props || res.uses$$props;
7972
8078
  uses$$restProps = uses$$restProps || res.uses$$restProps;
7973
8079
  uses$$slots = uses$$slots || res.uses$$slots;
@@ -7996,7 +8102,8 @@ function svelte2tsx(svelte, options = { parse }) {
7996
8102
  hasTopLevelAwait,
7997
8103
  svelte5Plus,
7998
8104
  isTsFile,
7999
- mode: options.mode
8105
+ mode: options.mode,
8106
+ emitJsDoc
8000
8107
  });
8001
8108
  // we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items
8002
8109
  if (moduleScriptTag) {
@@ -8049,7 +8156,8 @@ function svelte2tsx(svelte, options = { parse }) {
8049
8156
  generics,
8050
8157
  isSvelte5: svelte5Plus,
8051
8158
  hasTopLevelAwait,
8052
- noSvelteComponentTyped: options.noSvelteComponentTyped
8159
+ noSvelteComponentTyped: options.noSvelteComponentTyped,
8160
+ emitJsDoc
8053
8161
  });
8054
8162
  if (options.mode === 'dts') {
8055
8163
  // Prepend the import which is used for TS files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte2tsx",
3
- "version": "0.7.46",
3
+ "version": "0.7.48",
4
4
  "description": "Convert Svelte components to TSX for type checking",
5
5
  "author": "The Svelte Community",
6
6
  "license": "MIT",