svelte2tsx 0.6.25 → 0.6.27

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.
package/index.d.ts CHANGED
@@ -79,6 +79,11 @@ export function svelte2tsx(
79
79
  * The Svelte parser to use. Defaults to the one bundled with `svelte2tsx`.
80
80
  */
81
81
  parse?: typeof import('svelte/compiler').parse;
82
+ /**
83
+ * The VERSION from 'svelte/compiler'. Defaults to the one bundled with `svelte2tsx`.
84
+ * Transpiled output may vary between versions.
85
+ */
86
+ version?: string;
82
87
  }
83
88
  ): SvelteCompiledToTsx
84
89
 
package/index.js CHANGED
@@ -1479,8 +1479,11 @@ function parseAttributes(str, start) {
1479
1479
  }
1480
1480
  return attrs;
1481
1481
  }
1482
+ // Regex ensures that attributes with > characters in them still result in the content being matched correctly
1483
+ const scriptRegex = /(<!--[^]*?-->)|(<script((?:\s+[^=>'"\/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/]+)*\s*)>)([\S\s]*?)<\/script>/g;
1484
+ const styleRegex = /(<!--[^]*?-->)|(<style((?:\s+[^=>'"\/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/]+)*\s*)>)([\S\s]*?)<\/style>/g;
1482
1485
  function extractTag(htmlx, tag) {
1483
- const exp = new RegExp(`(<!--[^]*?-->)|(<${tag}([\\S\\s]*?)>)([\\S\\s]*?)<\\/${tag}>`, 'g');
1486
+ const exp = tag === 'script' ? scriptRegex : styleRegex;
1484
1487
  const matches = [];
1485
1488
  let match = null;
1486
1489
  while ((match = exp.exec(htmlx)) != null) {
@@ -1677,9 +1680,12 @@ transformations) {
1677
1680
  str.move(moves[i][0], moves[i][1], end);
1678
1681
  }
1679
1682
  let removeStart = start;
1680
- for (const transformation of [...moves].sort((t1, t2) => t1[0] - t2[0])) {
1683
+ const sortedMoves = [...moves].sort((t1, t2) => t1[0] - t2[0]);
1684
+ for (const transformation of sortedMoves) {
1681
1685
  if (removeStart < transformation[0]) {
1682
- if (deletePos !== moves.length && removeStart > deleteDest) {
1686
+ if (deletePos !== moves.length &&
1687
+ removeStart > deleteDest &&
1688
+ !(removeStart < end && transformation[0] >= end)) {
1683
1689
  str.move(removeStart, transformation[0], end);
1684
1690
  }
1685
1691
  if (transformation[0] < end) {
@@ -1692,7 +1698,8 @@ transformations) {
1692
1698
  if (removeStart > end) {
1693
1699
  // Reset the end to the last transformation before the end if there were transformations after the end
1694
1700
  // so we still delete the correct range afterwards
1695
- removeStart = (_b = (_a = moves.find((m) => m[1] < end)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : end;
1701
+ let idx = sortedMoves.findIndex((m) => m[0] > end) - 1;
1702
+ removeStart = (_b = (_a = sortedMoves[idx]) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : end;
1696
1703
  }
1697
1704
  if (removeStart < end) {
1698
1705
  // Completely delete the first character afterwards. This makes the mapping more correct,
@@ -2388,7 +2395,9 @@ function handleAttribute(str, attr, parent, preserveCase, element) {
2388
2395
  * lowercase the attribute name to make it adhere to our intrinsic elements definition
2389
2396
  */
2390
2397
  const transformAttributeCase = (name) => {
2391
- if (!preserveCase && !svgAttributes.find((x) => x == name)) {
2398
+ if (!preserveCase &&
2399
+ !svgAttributes.find((x) => x == name) &&
2400
+ !(element instanceof Element && element.tagName.includes('-'))) {
2392
2401
  return name.toLowerCase();
2393
2402
  }
2394
2403
  else {
@@ -2440,9 +2449,10 @@ function handleAttribute(str, attr, parent, preserveCase, element) {
2440
2449
  addAttribute(attributeName, [[attrVal.start - 1, attrVal.end + 1]]);
2441
2450
  return;
2442
2451
  }
2443
- const hasBrackets = str.original.lastIndexOf('}', attrVal.end) === attrVal.end - 1 ||
2444
- str.original.lastIndexOf('}"', attrVal.end) === attrVal.end - 1 ||
2445
- str.original.lastIndexOf("}'", attrVal.end) === attrVal.end - 1;
2452
+ const lastCharIndex = attrVal.end - 1;
2453
+ const hasBrackets = str.original[lastCharIndex] === '}' ||
2454
+ ((str.original[lastCharIndex] === '"' || str.original[lastCharIndex] === "'") &&
2455
+ str.original[lastCharIndex - 1] === '}');
2446
2456
  const needsNumberConversion = !hasBrackets &&
2447
2457
  parent.type === 'Element' &&
2448
2458
  numberOnlyAttributes.has(attr.name.toLowerCase()) &&
@@ -2644,7 +2654,12 @@ function handleBinding(str, attr, parent, element, preserveBind) {
2644
2654
  : // Other typings - remove the bind: prefix
2645
2655
  isShorthand
2646
2656
  ? [[attr.expression.start, attr.expression.end]]
2647
- : [[attr.start + 'bind:'.length, str.original.lastIndexOf('=', attr.expression.start)]];
2657
+ : [
2658
+ [
2659
+ attr.start + 'bind:'.length,
2660
+ str.original.lastIndexOf('=', attr.expression.start)
2661
+ ]
2662
+ ];
2648
2663
  const value = isShorthand
2649
2664
  ? preserveBind && element instanceof Element
2650
2665
  ? [rangeWithTrailingPropertyAccess(str.original, attr.expression)]
@@ -2725,21 +2740,23 @@ function handleEach(str, eachBlock) {
2725
2740
  const containsComma = str.original
2726
2741
  .substring(eachBlock.expression.start, eachBlock.expression.end)
2727
2742
  .includes(',');
2728
- const arrayAndItemVarTheSame = str.original.substring(eachBlock.expression.start, eachBlock.expression.end) ===
2729
- str.original.substring(eachBlock.context.start, eachBlock.context.end);
2743
+ const expressionEnd = getEnd(eachBlock.expression, str);
2744
+ const contextEnd = getEnd(eachBlock.context, str);
2745
+ const arrayAndItemVarTheSame = str.original.substring(eachBlock.expression.start, expressionEnd) ===
2746
+ str.original.substring(eachBlock.context.start, contextEnd);
2730
2747
  if (arrayAndItemVarTheSame) {
2731
2748
  transforms = [
2732
2749
  `{ const $$_each = __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
2733
2750
  [eachBlock.expression.start, eachBlock.expression.end],
2734
2751
  `${containsComma ? ')' : ''}); for(let `,
2735
- [eachBlock.context.start, eachBlock.context.end],
2752
+ [eachBlock.context.start, contextEnd],
2736
2753
  ' of $$_each){'
2737
2754
  ];
2738
2755
  }
2739
2756
  else {
2740
2757
  transforms = [
2741
2758
  'for(let ',
2742
- [eachBlock.context.start, eachBlock.context.end],
2759
+ [eachBlock.context.start, contextEnd],
2743
2760
  ` of __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
2744
2761
  [eachBlock.expression.start, eachBlock.expression.end],
2745
2762
  `${containsComma ? ')' : ''})){`
@@ -2770,6 +2787,14 @@ function handleEach(str, eachBlock) {
2770
2787
  });
2771
2788
  }
2772
2789
  }
2790
+ /**
2791
+ * Get the end of the node, excluding the type annotation
2792
+ */
2793
+ function getEnd(node, str) {
2794
+ return node.typeAnnotation
2795
+ ? str.original.lastIndexOf(':', node.typeAnnotation.start)
2796
+ : node.end;
2797
+ }
2773
2798
 
2774
2799
  /**
2775
2800
  * Transform on:xxx={yyy}
@@ -3034,14 +3059,15 @@ function handleTransitionDirective(str, attr, element) {
3034
3059
  * }
3035
3060
  * ```
3036
3061
  */
3037
- function handleSnippet(str, snippetBlock, element) {
3062
+ function handleSnippet(str, snippetBlock, component) {
3038
3063
  var _a;
3039
3064
  const endSnippet = str.original.lastIndexOf('{', snippetBlock.end - 1);
3040
- str.overwrite(endSnippet, snippetBlock.end, '}', {
3065
+ // Return something to silence the "snippet type not assignable to return type void" error
3066
+ str.overwrite(endSnippet, snippetBlock.end, 'return __sveltets_2_any(0)}', {
3041
3067
  contentOnly: true
3042
3068
  });
3043
3069
  const startEnd = str.original.indexOf('}', ((_a = snippetBlock.context) === null || _a === void 0 ? void 0 : _a.end) || snippetBlock.expression.end) + 1;
3044
- if (element !== undefined) {
3070
+ if (component !== undefined) {
3045
3071
  str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { contentOnly: true });
3046
3072
  const transforms = ['('];
3047
3073
  if (snippetBlock.context) {
@@ -3056,13 +3082,20 @@ function handleSnippet(str, snippetBlock, element) {
3056
3082
  }
3057
3083
  transforms.push(') => {');
3058
3084
  transforms.push([startEnd, snippetBlock.end]);
3059
- element.addProp([[snippetBlock.expression.start, snippetBlock.expression.end]], transforms);
3085
+ component.addProp([[snippetBlock.expression.start, snippetBlock.expression.end]], transforms);
3060
3086
  }
3061
3087
  else {
3088
+ const generic = snippetBlock.context
3089
+ ? snippetBlock.context.typeAnnotation
3090
+ ? `<${str.original.slice(snippetBlock.context.typeAnnotation.start, snippetBlock.context.typeAnnotation.end)}>`
3091
+ : // slap any on to it to silence "implicit any" errors; JSDoc people can't add types to snippets
3092
+ '<any>'
3093
+ : '';
3094
+ const typeAnnotation = surroundWithIgnoreComments(`: import('svelte').Snippet${generic}`);
3062
3095
  const transforms = [
3063
- 'const ',
3096
+ 'var ',
3064
3097
  [snippetBlock.expression.start, snippetBlock.expression.end],
3065
- ' = ('
3098
+ typeAnnotation + ' = ('
3066
3099
  ];
3067
3100
  if (snippetBlock.context) {
3068
3101
  transforms.push([snippetBlock.context.start, snippetBlock.context.end]);
@@ -3071,17 +3104,53 @@ function handleSnippet(str, snippetBlock, element) {
3071
3104
  transform(str, snippetBlock.start, startEnd, startEnd, transforms);
3072
3105
  }
3073
3106
  }
3107
+ function handleImplicitChildren(componentNode, component) {
3108
+ var _a;
3109
+ if (((_a = componentNode.children) === null || _a === void 0 ? void 0 : _a.length) === 0) {
3110
+ return;
3111
+ }
3112
+ let hasSlot = false;
3113
+ for (const child of componentNode.children) {
3114
+ if (child.type === 'SvelteSelf' ||
3115
+ child.type === 'InlineComponent' ||
3116
+ child.type === 'Element' ||
3117
+ child.type === 'SlotTemplate') {
3118
+ if (child.attributes.some((a) => {
3119
+ var _a;
3120
+ return a.type === 'Attribute' &&
3121
+ a.name === 'slot' &&
3122
+ ((_a = a.value[0]) === null || _a === void 0 ? void 0 : _a.data) !== 'default';
3123
+ })) {
3124
+ continue;
3125
+ }
3126
+ }
3127
+ if (child.type === 'Text' && child.data.trim() === '') {
3128
+ continue;
3129
+ }
3130
+ if (child.type !== 'SnippetBlock') {
3131
+ hasSlot = true;
3132
+ break;
3133
+ }
3134
+ }
3135
+ if (!hasSlot) {
3136
+ return;
3137
+ }
3138
+ // it's enough to fake a children prop, we don't need to actually move the content inside (which would also reset control flow)
3139
+ component.addProp(['children'], ['() => { return __sveltets_2_any(0); }']);
3140
+ }
3074
3141
 
3075
3142
  /**
3076
3143
  * `{@render foo(x)}` --> `;foo(x);`
3077
3144
  */
3078
3145
  function handleRenderTag(str, renderTag) {
3079
- str.overwrite(renderTag.start, renderTag.expression.start, ';', { contentOnly: true });
3146
+ str.overwrite(renderTag.start, renderTag.expression.start, ';__sveltets_2_ensureSnippet(', {
3147
+ contentOnly: true
3148
+ });
3080
3149
  if (renderTag.argument) {
3081
- str.overwrite(withTrailingPropertyAccess(str.original, renderTag.argument.end), renderTag.end, ');');
3150
+ str.overwrite(withTrailingPropertyAccess(str.original, renderTag.argument.end), renderTag.end, '));');
3082
3151
  }
3083
3152
  else {
3084
- str.overwrite(withTrailingPropertyAccess(str.original, renderTag.expression.end), renderTag.end, '();');
3153
+ str.overwrite(withTrailingPropertyAccess(str.original, renderTag.expression.end), renderTag.end, '());');
3085
3154
  }
3086
3155
  }
3087
3156
 
@@ -3096,11 +3165,12 @@ function stripDoctype(str) {
3096
3165
  * Walks the HTMLx part of the Svelte component
3097
3166
  * and converts it to JSX
3098
3167
  */
3099
- function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}) {
3168
+ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = { svelte5Plus: false }) {
3100
3169
  str.original;
3101
3170
  options = { preserveAttributeCase: false, ...options };
3102
3171
  options.typingsNamespace = options.typingsNamespace || 'svelteHTML';
3103
3172
  stripDoctype(str);
3173
+ const rootSnippets = [];
3104
3174
  let element;
3105
3175
  walk(ast, {
3106
3176
  enter: (estreeTypedNode, estreeTypedParent, prop, index) => {
@@ -3125,6 +3195,10 @@ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}
3125
3195
  estreeTypedParent.type === 'InlineComponent'
3126
3196
  ? element
3127
3197
  : undefined);
3198
+ if (!element) {
3199
+ // root snippet -> move to instance script
3200
+ rootSnippets.push([node.start, node.end]);
3201
+ }
3128
3202
  break;
3129
3203
  case 'MustacheTag':
3130
3204
  handleMustacheTag(str, node, parent);
@@ -3149,6 +3223,9 @@ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}
3149
3223
  else {
3150
3224
  element = new InlineComponent(str, node);
3151
3225
  }
3226
+ if (options.svelte5Plus) {
3227
+ handleImplicitChildren(node, element);
3228
+ }
3152
3229
  break;
3153
3230
  case 'Element':
3154
3231
  case 'Options':
@@ -3253,6 +3330,7 @@ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}
3253
3330
  }
3254
3331
  }
3255
3332
  });
3333
+ return rootSnippets;
3256
3334
  }
3257
3335
 
3258
3336
  /**
@@ -4255,10 +4333,11 @@ function is$$PropsDeclaration(node) {
4255
4333
  return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Props';
4256
4334
  }
4257
4335
  class ExportedNames {
4258
- constructor(str, astOffset, basename) {
4336
+ constructor(str, astOffset, basename, isTsFile) {
4259
4337
  this.str = str;
4260
4338
  this.astOffset = astOffset;
4261
4339
  this.basename = basename;
4340
+ this.isTsFile = isTsFile;
4262
4341
  /**
4263
4342
  * Uses the `$$Props` type
4264
4343
  */
@@ -4349,16 +4428,89 @@ class ExportedNames {
4349
4428
  this.$props.generic = node.initializer.typeArguments[0].getText();
4350
4429
  }
4351
4430
  else {
4352
- const nodeText = node.getFullText();
4353
- let comments = (_b = ts
4354
- .getLeadingCommentRanges(nodeText, 0)) === null || _b === void 0 ? void 0 : _b.map((c) => nodeText.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4355
- if (!comments) {
4356
- const parentText = node.parent.getFullText();
4357
- comments = (_c = ts
4358
- .getLeadingCommentRanges(parentText, node.parent.pos)) === null || _c === void 0 ? void 0 : _c.map((c) => parentText.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4431
+ if (!this.isTsFile) {
4432
+ const text = node.getSourceFile().getFullText();
4433
+ let comments = (_b = ts
4434
+ .getLeadingCommentRanges(text, node.pos)) === null || _b === void 0 ? void 0 : _b.map((c) => text.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4435
+ if (!comments) {
4436
+ comments = (_c = ts
4437
+ .getLeadingCommentRanges(text, node.parent.pos)) === null || _c === void 0 ? void 0 : _c.map((c) => text.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4438
+ }
4439
+ // We don't bother extracting the type, we just use the comment as-is
4440
+ this.$props.comment = comments || '';
4441
+ }
4442
+ if (this.$props.comment) {
4443
+ return;
4444
+ }
4445
+ if (internalHelpers.isKitRouteFile(this.basename)) {
4446
+ const kitType = this.basename.includes('layout')
4447
+ ? `{ data: import('./$types.js').LayoutData, form: import('./$types.js').ActionData, children: import('svelte').Snippet }`
4448
+ : `{ data: import('./$types.js').PageData, form: import('./$types.js').ActionData }`;
4449
+ if (this.isTsFile) {
4450
+ this.$props.generic = kitType;
4451
+ preprendStr(this.str, node.initializer.expression.end + this.astOffset, surroundWithIgnoreComments(`<${kitType}>`));
4452
+ }
4453
+ else {
4454
+ this.$props.comment = `/** @type {${kitType}} */`;
4455
+ preprendStr(this.str, node.pos + this.astOffset, this.$props.comment);
4456
+ }
4457
+ }
4458
+ else {
4459
+ // Do a best-effort to extract the props from the object literal
4460
+ let propsStr = '';
4461
+ let withUnknown = false;
4462
+ let props = [];
4463
+ if (ts.isObjectBindingPattern(node.name)) {
4464
+ for (const element of node.name.elements) {
4465
+ if (!ts.isIdentifier(element.name) || !!element.dotDotDotToken) {
4466
+ withUnknown = true;
4467
+ }
4468
+ else {
4469
+ if (element.initializer) {
4470
+ const type = ts.isAsExpression(element.initializer)
4471
+ ? element.initializer.type.getText()
4472
+ : ts.isStringLiteral(element.initializer)
4473
+ ? 'string'
4474
+ : ts.isNumericLiteral(element.initializer)
4475
+ ? 'number'
4476
+ : element.initializer.kind === ts.SyntaxKind.TrueKeyword ||
4477
+ element.initializer.kind === ts.SyntaxKind.FalseKeyword
4478
+ ? 'boolean'
4479
+ : ts.isIdentifier(element.initializer)
4480
+ ? `typeof ${element.initializer.text}`
4481
+ : 'unknown';
4482
+ props.push(`${element.name.text}?: ${type}`);
4483
+ }
4484
+ else {
4485
+ props.push(`${element.name.text}: unknown`);
4486
+ }
4487
+ }
4488
+ }
4489
+ }
4490
+ if (props.length > 0) {
4491
+ propsStr =
4492
+ `{ ${props.join(', ')} }` +
4493
+ (withUnknown ? ' & Record<string, unknown>' : '');
4494
+ }
4495
+ else if (withUnknown) {
4496
+ propsStr = 'Record<string, unknown>';
4497
+ }
4498
+ else {
4499
+ propsStr = 'Record<string, never>';
4500
+ }
4501
+ if (this.isTsFile) {
4502
+ this.$props.generic = propsStr;
4503
+ if (props.length > 0 || withUnknown) {
4504
+ preprendStr(this.str, node.initializer.expression.end + this.astOffset, surroundWithIgnoreComments(`<${propsStr}>`));
4505
+ }
4506
+ }
4507
+ else {
4508
+ this.$props.comment = `/** @type {${propsStr}} */`;
4509
+ if (props.length > 0 || withUnknown) {
4510
+ preprendStr(this.str, node.pos + this.astOffset, this.$props.comment);
4511
+ }
4512
+ }
4359
4513
  }
4360
- // We don't bother extracting the type, we just use the comment as-is
4361
- this.$props.comment = comments || '';
4362
4514
  }
4363
4515
  }
4364
4516
  removeExport(start, end) {
@@ -4511,6 +4663,14 @@ class ExportedNames {
4511
4663
  });
4512
4664
  }
4513
4665
  }
4666
+ addImplicitChildrenExport(hasAttributes) {
4667
+ if (this.exports.has('children'))
4668
+ return;
4669
+ this.exports.set('children', {
4670
+ isLet: true,
4671
+ implicitChildren: hasAttributes ? 'attributes' : 'empty'
4672
+ });
4673
+ }
4514
4674
  /**
4515
4675
  * Adds export to map
4516
4676
  */
@@ -4570,7 +4730,7 @@ class ExportedNames {
4570
4730
  * @param isTsFile Whether this is a TypeScript file or not.
4571
4731
  * @param uses$$propsOr$$restProps whether the file references the $$props or $$restProps variable
4572
4732
  */
4573
- createPropsStr(isTsFile, uses$$propsOr$$restProps) {
4733
+ createPropsStr(uses$$propsOr$$restProps) {
4574
4734
  const names = Array.from(this.exports.entries());
4575
4735
  if (this.$props.generic) {
4576
4736
  const others = names.filter(([, { isLet }]) => !isLet);
@@ -4606,11 +4766,11 @@ class ExportedNames {
4606
4766
  }
4607
4767
  if (names.length === 0 && !uses$$propsOr$$restProps) {
4608
4768
  // Necessary, because {} roughly equals to any
4609
- return isTsFile
4769
+ return this.isTsFile
4610
4770
  ? '{} as Record<string, never>'
4611
4771
  : '/** @type {Record<string, never>} */ ({})';
4612
4772
  }
4613
- const dontAddTypeDef = !isTsFile || names.every(([_, value]) => !value.type && value.required);
4773
+ const dontAddTypeDef = !this.isTsFile || names.every(([_, value]) => !value.type && value.required);
4614
4774
  const returnElements = this.createReturnElements(names, dontAddTypeDef);
4615
4775
  if (dontAddTypeDef) {
4616
4776
  // Only `typeof` exports -> omit the `as {...}` completely.
@@ -4622,12 +4782,22 @@ class ExportedNames {
4622
4782
  }
4623
4783
  createReturnElements(names, dontAddTypeDef) {
4624
4784
  return names.map(([key, value]) => {
4785
+ if (value.implicitChildren) {
4786
+ return `children?: ${value.implicitChildren === 'empty'
4787
+ ? '__sveltets_2_snippet()'
4788
+ : '$$implicit_children'}`;
4789
+ }
4625
4790
  // Important to not use shorthand props for rename functionality
4626
4791
  return `${dontAddTypeDef && value.doc ? `\n${value.doc}` : ''}${value.identifierText || key}: ${key}`;
4627
4792
  });
4628
4793
  }
4629
4794
  createReturnElementsType(names) {
4630
4795
  return names.map(([key, value]) => {
4796
+ if (value.implicitChildren) {
4797
+ return `children?: ${value.implicitChildren === 'empty'
4798
+ ? `import('svelte').Snippet`
4799
+ : 'typeof $$implicit_children'}`;
4800
+ }
4631
4801
  const identifier = `${value.doc ? `\n${value.doc}` : ''}${value.identifierText || key}${value.required ? '' : '?'}`;
4632
4802
  if (!value.type) {
4633
4803
  return `${identifier}: typeof ${key}`;
@@ -4643,6 +4813,9 @@ class ExportedNames {
4643
4813
  getExportsMap() {
4644
4814
  return this.exports;
4645
4815
  }
4816
+ uses$propsRune() {
4817
+ return !!this.$props.generic || !!this.$props.comment;
4818
+ }
4646
4819
  }
4647
4820
 
4648
4821
  function isMember(parent, prop) {
@@ -5588,12 +5761,12 @@ class InterfacesAndTypes {
5588
5761
  }
5589
5762
  }
5590
5763
 
5591
- function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, hasModuleScript, basename) {
5764
+ function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, hasModuleScript, isTSFile, basename) {
5592
5765
  const htmlx = str.original;
5593
5766
  const scriptContent = htmlx.substring(script.content.start, script.content.end);
5594
5767
  const tsAst = ts.createSourceFile('component.ts.svelte', scriptContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
5595
5768
  const astOffset = script.content.start;
5596
- const exportedNames = new ExportedNames(str, astOffset, basename);
5769
+ const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile);
5597
5770
  const generics = new Generics(str, astOffset, script);
5598
5771
  const interfacesAndTypes = new InterfacesAndTypes();
5599
5772
  const implicitTopLevelNames = new ImplicitTopLevelNames(str, astOffset);
@@ -6042,7 +6215,7 @@ function classNameFromFilename(filename, appendSuffix) {
6042
6215
  }
6043
6216
  }
6044
6217
 
6045
- function createRenderFunction({ str, scriptTag, scriptDestination, slots, events, exportedNames, isTsFile, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, mode }) {
6218
+ function createRenderFunction({ str, scriptTag, scriptDestination, rootSnippets, slots, events, exportedNames, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, svelte5Plus, mode }) {
6046
6219
  const htmlx = str.original;
6047
6220
  let propsDecl = '';
6048
6221
  if (uses$$props) {
@@ -6089,6 +6262,9 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6089
6262
  str.overwrite(scriptTag.start + 1, scriptTagEnd, `function render${generics.toDefinitionString(true)}() {${propsDecl}\n`);
6090
6263
  }
6091
6264
  const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
6265
+ for (const rootSnippet of rootSnippets) {
6266
+ str.move(rootSnippet[0], rootSnippet[1], scriptEndTagStart);
6267
+ }
6092
6268
  // wrap template with callback
6093
6269
  str.overwrite(scriptEndTagStart, scriptTag.end, `${slotsDeclaration};\nasync () => {`, {
6094
6270
  contentOnly: true
@@ -6102,22 +6278,32 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6102
6278
  : '{' +
6103
6279
  Array.from(slots.entries())
6104
6280
  .map(([name, attrs]) => {
6105
- const attrsAsString = Array.from(attrs.entries())
6106
- .map(([exportName, expr]) => exportName.startsWith('__spread__')
6107
- ? `...${expr}`
6108
- : `${exportName}:${expr}`)
6109
- .join(', ');
6110
- return `'${name}': {${attrsAsString}}`;
6281
+ return `'${name}': {${slotAttributesToString(attrs)}}`;
6111
6282
  })
6112
6283
  .join(', ') +
6113
6284
  '}';
6114
- const returnString = `\nreturn { props: ${exportedNames.createPropsStr(isTsFile, uses$$props || uses$$restProps)}` +
6285
+ const needsImplicitChildrenProp = svelte5Plus &&
6286
+ !exportedNames.uses$propsRune() &&
6287
+ slots.has('default') &&
6288
+ !exportedNames.getExportsMap().has('default');
6289
+ if (needsImplicitChildrenProp) {
6290
+ exportedNames.addImplicitChildrenExport(slots.get('default').size > 0);
6291
+ }
6292
+ const returnString = `${needsImplicitChildrenProp && slots.get('default').size > 0
6293
+ ? `\nlet $$implicit_children = __sveltets_2_snippet({${slotAttributesToString(slots.get('default'))}});`
6294
+ : ''}` +
6295
+ `\nreturn { props: ${exportedNames.createPropsStr(uses$$props || uses$$restProps)}` +
6115
6296
  `, slots: ${slotsAsDef}` +
6116
6297
  `, events: ${events.toDefString()} }}`;
6117
6298
  // wrap template with callback
6118
6299
  str.append('};');
6119
6300
  str.append(returnString);
6120
6301
  }
6302
+ function slotAttributesToString(attrs) {
6303
+ return Array.from(attrs.entries())
6304
+ .map(([exportName, expr]) => exportName.startsWith('__spread__') ? `...${expr}` : `${exportName}:${expr}`)
6305
+ .join(', ');
6306
+ }
6121
6307
 
6122
6308
  function processSvelteTemplate(str, parse, options) {
6123
6309
  const { htmlxAst, tags } = parseHtmlx(str.original, parse, options);
@@ -6303,9 +6489,10 @@ function processSvelteTemplate(str, parse, options) {
6303
6489
  break;
6304
6490
  }
6305
6491
  };
6306
- convertHtmlxToJsx(str, htmlxAst, onHtmlxWalk, onHtmlxLeave, {
6492
+ const rootSnippets = convertHtmlxToJsx(str, htmlxAst, onHtmlxWalk, onHtmlxLeave, {
6307
6493
  preserveAttributeCase: (options === null || options === void 0 ? void 0 : options.namespace) == 'foreign',
6308
- typingsNamespace: options.typingsNamespace
6494
+ typingsNamespace: options.typingsNamespace,
6495
+ svelte5Plus: options.svelte5Plus
6309
6496
  });
6310
6497
  // resolve scripts
6311
6498
  const { scriptTag, moduleScriptTag } = scripts.getTopLevelScriptTags();
@@ -6318,6 +6505,7 @@ function processSvelteTemplate(str, parse, options) {
6318
6505
  htmlAst: htmlxAst,
6319
6506
  moduleScriptTag,
6320
6507
  scriptTag,
6508
+ rootSnippets,
6321
6509
  slots: slotHandler.getSlotDef(),
6322
6510
  events: new ComponentEvents(eventHandler, tags.some((tag) => { var _a; return (_a = tag.attributes) === null || _a === void 0 ? void 0 : _a.some((a) => a.name === 'strictEvents'); }), str),
6323
6511
  uses$$props,
@@ -6330,10 +6518,15 @@ function processSvelteTemplate(str, parse, options) {
6330
6518
  }
6331
6519
  function svelte2tsx(svelte, options = { parse: compiler.parse }) {
6332
6520
  options.mode = options.mode || 'ts';
6521
+ options.version = options.version || compiler.VERSION;
6333
6522
  const str = new MagicString(svelte);
6334
6523
  const basename = path.basename(options.filename || '');
6524
+ const svelte5Plus = Number(options.version[0]) > 4;
6335
6525
  // process the htmlx as a svelte template
6336
- let { htmlAst, moduleScriptTag, scriptTag, slots, uses$$props, uses$$slots, uses$$restProps, events, componentDocumentation, resolvedStores, usesAccessors } = processSvelteTemplate(str, options.parse || compiler.parse, options);
6526
+ let { htmlAst, moduleScriptTag, scriptTag, rootSnippets, slots, uses$$props, uses$$slots, uses$$restProps, events, componentDocumentation, resolvedStores, usesAccessors } = processSvelteTemplate(str, options.parse || compiler.parse, {
6527
+ ...options,
6528
+ svelte5Plus
6529
+ });
6337
6530
  /* Rearrange the script tags so that module is first, and instance second followed finally by the template
6338
6531
  * This is a bit convoluted due to some trouble I had with magic string. A simple str.move(start,end,0) for each script wasn't enough
6339
6532
  * since if the module script was already at 0, it wouldn't move (which is fine) but would mean the order would be swapped when the script tag tried to move to 0
@@ -6355,7 +6548,7 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
6355
6548
  : instanceScriptTarget;
6356
6549
  const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart);
6357
6550
  //move the instance script and process the content
6358
- let exportedNames = new ExportedNames(str, 0, basename);
6551
+ let exportedNames = new ExportedNames(str, 0, basename, options === null || options === void 0 ? void 0 : options.isTsFile);
6359
6552
  let generics = new Generics(str, 0, { attributes: [] });
6360
6553
  let uses$$SlotsInterface = false;
6361
6554
  if (scriptTag) {
@@ -6364,7 +6557,7 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
6364
6557
  str.move(scriptTag.start, scriptTag.end, instanceScriptTarget);
6365
6558
  }
6366
6559
  const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues, options.mode,
6367
- /**hasModuleScripts */ !!moduleScriptTag, basename);
6560
+ /**hasModuleScripts */ !!moduleScriptTag, options === null || options === void 0 ? void 0 : options.isTsFile, basename);
6368
6561
  uses$$props = uses$$props || res.uses$$props;
6369
6562
  uses$$restProps = uses$$restProps || res.uses$$restProps;
6370
6563
  uses$$slots = uses$$slots || res.uses$$slots;
@@ -6375,15 +6568,16 @@ function svelte2tsx(svelte, options = { parse: compiler.parse }) {
6375
6568
  str,
6376
6569
  scriptTag,
6377
6570
  scriptDestination: instanceScriptTarget,
6571
+ rootSnippets,
6378
6572
  slots,
6379
6573
  events,
6380
6574
  exportedNames,
6381
- isTsFile: options === null || options === void 0 ? void 0 : options.isTsFile,
6382
6575
  uses$$props,
6383
6576
  uses$$restProps,
6384
6577
  uses$$slots,
6385
6578
  uses$$SlotsInterface,
6386
6579
  generics,
6580
+ svelte5Plus,
6387
6581
  mode: options.mode
6388
6582
  });
6389
6583
  // we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items
@@ -6490,13 +6684,13 @@ function loadTsconfig(config, svelteMap) {
6490
6684
  return {
6491
6685
  options: {
6492
6686
  ...options,
6493
- noEmit: false,
6687
+ noEmit: false, // Set to true in case of jsconfig, force false, else nothing is emitted
6494
6688
  moduleResolution:
6495
6689
  // NodeJS: up to 4.9, Node10: since 5.0
6496
- (_a = ts.ModuleResolutionKind.NodeJs) !== null && _a !== void 0 ? _a : ts.ModuleResolutionKind.Node10,
6497
- declaration: true,
6498
- emitDeclarationOnly: true,
6499
- declarationDir: config.declarationDir,
6690
+ (_a = ts.ModuleResolutionKind.NodeJs) !== null && _a !== void 0 ? _a : ts.ModuleResolutionKind.Node10, // Classic if not set, which gives wrong results
6691
+ declaration: true, // Needed for d.ts file generation
6692
+ emitDeclarationOnly: true, // We only want d.ts file generation
6693
+ declarationDir: config.declarationDir, // Where to put the declarations
6500
6694
  allowNonTsExtensions: true
6501
6695
  },
6502
6696
  filenames
package/index.mjs CHANGED
@@ -3,7 +3,7 @@ import ts from 'typescript';
3
3
  import * as path from 'path';
4
4
  import path__default from 'path';
5
5
  import { pascalCase } from 'pascal-case';
6
- import { parse } from 'svelte/compiler';
6
+ import { VERSION, parse } from 'svelte/compiler';
7
7
 
8
8
  const comma = ','.charCodeAt(0);
9
9
  const semicolon = ';'.charCodeAt(0);
@@ -1459,8 +1459,11 @@ function parseAttributes(str, start) {
1459
1459
  }
1460
1460
  return attrs;
1461
1461
  }
1462
+ // Regex ensures that attributes with > characters in them still result in the content being matched correctly
1463
+ const scriptRegex = /(<!--[^]*?-->)|(<script((?:\s+[^=>'"\/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/]+)*\s*)>)([\S\s]*?)<\/script>/g;
1464
+ const styleRegex = /(<!--[^]*?-->)|(<style((?:\s+[^=>'"\/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/]+)*\s*)>)([\S\s]*?)<\/style>/g;
1462
1465
  function extractTag(htmlx, tag) {
1463
- const exp = new RegExp(`(<!--[^]*?-->)|(<${tag}([\\S\\s]*?)>)([\\S\\s]*?)<\\/${tag}>`, 'g');
1466
+ const exp = tag === 'script' ? scriptRegex : styleRegex;
1464
1467
  const matches = [];
1465
1468
  let match = null;
1466
1469
  while ((match = exp.exec(htmlx)) != null) {
@@ -1657,9 +1660,12 @@ transformations) {
1657
1660
  str.move(moves[i][0], moves[i][1], end);
1658
1661
  }
1659
1662
  let removeStart = start;
1660
- for (const transformation of [...moves].sort((t1, t2) => t1[0] - t2[0])) {
1663
+ const sortedMoves = [...moves].sort((t1, t2) => t1[0] - t2[0]);
1664
+ for (const transformation of sortedMoves) {
1661
1665
  if (removeStart < transformation[0]) {
1662
- if (deletePos !== moves.length && removeStart > deleteDest) {
1666
+ if (deletePos !== moves.length &&
1667
+ removeStart > deleteDest &&
1668
+ !(removeStart < end && transformation[0] >= end)) {
1663
1669
  str.move(removeStart, transformation[0], end);
1664
1670
  }
1665
1671
  if (transformation[0] < end) {
@@ -1672,7 +1678,8 @@ transformations) {
1672
1678
  if (removeStart > end) {
1673
1679
  // Reset the end to the last transformation before the end if there were transformations after the end
1674
1680
  // so we still delete the correct range afterwards
1675
- removeStart = (_b = (_a = moves.find((m) => m[1] < end)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : end;
1681
+ let idx = sortedMoves.findIndex((m) => m[0] > end) - 1;
1682
+ removeStart = (_b = (_a = sortedMoves[idx]) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : end;
1676
1683
  }
1677
1684
  if (removeStart < end) {
1678
1685
  // Completely delete the first character afterwards. This makes the mapping more correct,
@@ -2368,7 +2375,9 @@ function handleAttribute(str, attr, parent, preserveCase, element) {
2368
2375
  * lowercase the attribute name to make it adhere to our intrinsic elements definition
2369
2376
  */
2370
2377
  const transformAttributeCase = (name) => {
2371
- if (!preserveCase && !svgAttributes.find((x) => x == name)) {
2378
+ if (!preserveCase &&
2379
+ !svgAttributes.find((x) => x == name) &&
2380
+ !(element instanceof Element && element.tagName.includes('-'))) {
2372
2381
  return name.toLowerCase();
2373
2382
  }
2374
2383
  else {
@@ -2420,9 +2429,10 @@ function handleAttribute(str, attr, parent, preserveCase, element) {
2420
2429
  addAttribute(attributeName, [[attrVal.start - 1, attrVal.end + 1]]);
2421
2430
  return;
2422
2431
  }
2423
- const hasBrackets = str.original.lastIndexOf('}', attrVal.end) === attrVal.end - 1 ||
2424
- str.original.lastIndexOf('}"', attrVal.end) === attrVal.end - 1 ||
2425
- str.original.lastIndexOf("}'", attrVal.end) === attrVal.end - 1;
2432
+ const lastCharIndex = attrVal.end - 1;
2433
+ const hasBrackets = str.original[lastCharIndex] === '}' ||
2434
+ ((str.original[lastCharIndex] === '"' || str.original[lastCharIndex] === "'") &&
2435
+ str.original[lastCharIndex - 1] === '}');
2426
2436
  const needsNumberConversion = !hasBrackets &&
2427
2437
  parent.type === 'Element' &&
2428
2438
  numberOnlyAttributes.has(attr.name.toLowerCase()) &&
@@ -2624,7 +2634,12 @@ function handleBinding(str, attr, parent, element, preserveBind) {
2624
2634
  : // Other typings - remove the bind: prefix
2625
2635
  isShorthand
2626
2636
  ? [[attr.expression.start, attr.expression.end]]
2627
- : [[attr.start + 'bind:'.length, str.original.lastIndexOf('=', attr.expression.start)]];
2637
+ : [
2638
+ [
2639
+ attr.start + 'bind:'.length,
2640
+ str.original.lastIndexOf('=', attr.expression.start)
2641
+ ]
2642
+ ];
2628
2643
  const value = isShorthand
2629
2644
  ? preserveBind && element instanceof Element
2630
2645
  ? [rangeWithTrailingPropertyAccess(str.original, attr.expression)]
@@ -2705,21 +2720,23 @@ function handleEach(str, eachBlock) {
2705
2720
  const containsComma = str.original
2706
2721
  .substring(eachBlock.expression.start, eachBlock.expression.end)
2707
2722
  .includes(',');
2708
- const arrayAndItemVarTheSame = str.original.substring(eachBlock.expression.start, eachBlock.expression.end) ===
2709
- str.original.substring(eachBlock.context.start, eachBlock.context.end);
2723
+ const expressionEnd = getEnd(eachBlock.expression, str);
2724
+ const contextEnd = getEnd(eachBlock.context, str);
2725
+ const arrayAndItemVarTheSame = str.original.substring(eachBlock.expression.start, expressionEnd) ===
2726
+ str.original.substring(eachBlock.context.start, contextEnd);
2710
2727
  if (arrayAndItemVarTheSame) {
2711
2728
  transforms = [
2712
2729
  `{ const $$_each = __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
2713
2730
  [eachBlock.expression.start, eachBlock.expression.end],
2714
2731
  `${containsComma ? ')' : ''}); for(let `,
2715
- [eachBlock.context.start, eachBlock.context.end],
2732
+ [eachBlock.context.start, contextEnd],
2716
2733
  ' of $$_each){'
2717
2734
  ];
2718
2735
  }
2719
2736
  else {
2720
2737
  transforms = [
2721
2738
  'for(let ',
2722
- [eachBlock.context.start, eachBlock.context.end],
2739
+ [eachBlock.context.start, contextEnd],
2723
2740
  ` of __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
2724
2741
  [eachBlock.expression.start, eachBlock.expression.end],
2725
2742
  `${containsComma ? ')' : ''})){`
@@ -2750,6 +2767,14 @@ function handleEach(str, eachBlock) {
2750
2767
  });
2751
2768
  }
2752
2769
  }
2770
+ /**
2771
+ * Get the end of the node, excluding the type annotation
2772
+ */
2773
+ function getEnd(node, str) {
2774
+ return node.typeAnnotation
2775
+ ? str.original.lastIndexOf(':', node.typeAnnotation.start)
2776
+ : node.end;
2777
+ }
2753
2778
 
2754
2779
  /**
2755
2780
  * Transform on:xxx={yyy}
@@ -3014,14 +3039,15 @@ function handleTransitionDirective(str, attr, element) {
3014
3039
  * }
3015
3040
  * ```
3016
3041
  */
3017
- function handleSnippet(str, snippetBlock, element) {
3042
+ function handleSnippet(str, snippetBlock, component) {
3018
3043
  var _a;
3019
3044
  const endSnippet = str.original.lastIndexOf('{', snippetBlock.end - 1);
3020
- str.overwrite(endSnippet, snippetBlock.end, '}', {
3045
+ // Return something to silence the "snippet type not assignable to return type void" error
3046
+ str.overwrite(endSnippet, snippetBlock.end, 'return __sveltets_2_any(0)}', {
3021
3047
  contentOnly: true
3022
3048
  });
3023
3049
  const startEnd = str.original.indexOf('}', ((_a = snippetBlock.context) === null || _a === void 0 ? void 0 : _a.end) || snippetBlock.expression.end) + 1;
3024
- if (element !== undefined) {
3050
+ if (component !== undefined) {
3025
3051
  str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { contentOnly: true });
3026
3052
  const transforms = ['('];
3027
3053
  if (snippetBlock.context) {
@@ -3036,13 +3062,20 @@ function handleSnippet(str, snippetBlock, element) {
3036
3062
  }
3037
3063
  transforms.push(') => {');
3038
3064
  transforms.push([startEnd, snippetBlock.end]);
3039
- element.addProp([[snippetBlock.expression.start, snippetBlock.expression.end]], transforms);
3065
+ component.addProp([[snippetBlock.expression.start, snippetBlock.expression.end]], transforms);
3040
3066
  }
3041
3067
  else {
3068
+ const generic = snippetBlock.context
3069
+ ? snippetBlock.context.typeAnnotation
3070
+ ? `<${str.original.slice(snippetBlock.context.typeAnnotation.start, snippetBlock.context.typeAnnotation.end)}>`
3071
+ : // slap any on to it to silence "implicit any" errors; JSDoc people can't add types to snippets
3072
+ '<any>'
3073
+ : '';
3074
+ const typeAnnotation = surroundWithIgnoreComments(`: import('svelte').Snippet${generic}`);
3042
3075
  const transforms = [
3043
- 'const ',
3076
+ 'var ',
3044
3077
  [snippetBlock.expression.start, snippetBlock.expression.end],
3045
- ' = ('
3078
+ typeAnnotation + ' = ('
3046
3079
  ];
3047
3080
  if (snippetBlock.context) {
3048
3081
  transforms.push([snippetBlock.context.start, snippetBlock.context.end]);
@@ -3051,17 +3084,53 @@ function handleSnippet(str, snippetBlock, element) {
3051
3084
  transform(str, snippetBlock.start, startEnd, startEnd, transforms);
3052
3085
  }
3053
3086
  }
3087
+ function handleImplicitChildren(componentNode, component) {
3088
+ var _a;
3089
+ if (((_a = componentNode.children) === null || _a === void 0 ? void 0 : _a.length) === 0) {
3090
+ return;
3091
+ }
3092
+ let hasSlot = false;
3093
+ for (const child of componentNode.children) {
3094
+ if (child.type === 'SvelteSelf' ||
3095
+ child.type === 'InlineComponent' ||
3096
+ child.type === 'Element' ||
3097
+ child.type === 'SlotTemplate') {
3098
+ if (child.attributes.some((a) => {
3099
+ var _a;
3100
+ return a.type === 'Attribute' &&
3101
+ a.name === 'slot' &&
3102
+ ((_a = a.value[0]) === null || _a === void 0 ? void 0 : _a.data) !== 'default';
3103
+ })) {
3104
+ continue;
3105
+ }
3106
+ }
3107
+ if (child.type === 'Text' && child.data.trim() === '') {
3108
+ continue;
3109
+ }
3110
+ if (child.type !== 'SnippetBlock') {
3111
+ hasSlot = true;
3112
+ break;
3113
+ }
3114
+ }
3115
+ if (!hasSlot) {
3116
+ return;
3117
+ }
3118
+ // it's enough to fake a children prop, we don't need to actually move the content inside (which would also reset control flow)
3119
+ component.addProp(['children'], ['() => { return __sveltets_2_any(0); }']);
3120
+ }
3054
3121
 
3055
3122
  /**
3056
3123
  * `{@render foo(x)}` --> `;foo(x);`
3057
3124
  */
3058
3125
  function handleRenderTag(str, renderTag) {
3059
- str.overwrite(renderTag.start, renderTag.expression.start, ';', { contentOnly: true });
3126
+ str.overwrite(renderTag.start, renderTag.expression.start, ';__sveltets_2_ensureSnippet(', {
3127
+ contentOnly: true
3128
+ });
3060
3129
  if (renderTag.argument) {
3061
- str.overwrite(withTrailingPropertyAccess(str.original, renderTag.argument.end), renderTag.end, ');');
3130
+ str.overwrite(withTrailingPropertyAccess(str.original, renderTag.argument.end), renderTag.end, '));');
3062
3131
  }
3063
3132
  else {
3064
- str.overwrite(withTrailingPropertyAccess(str.original, renderTag.expression.end), renderTag.end, '();');
3133
+ str.overwrite(withTrailingPropertyAccess(str.original, renderTag.expression.end), renderTag.end, '());');
3065
3134
  }
3066
3135
  }
3067
3136
 
@@ -3076,11 +3145,12 @@ function stripDoctype(str) {
3076
3145
  * Walks the HTMLx part of the Svelte component
3077
3146
  * and converts it to JSX
3078
3147
  */
3079
- function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}) {
3148
+ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = { svelte5Plus: false }) {
3080
3149
  str.original;
3081
3150
  options = { preserveAttributeCase: false, ...options };
3082
3151
  options.typingsNamespace = options.typingsNamespace || 'svelteHTML';
3083
3152
  stripDoctype(str);
3153
+ const rootSnippets = [];
3084
3154
  let element;
3085
3155
  walk(ast, {
3086
3156
  enter: (estreeTypedNode, estreeTypedParent, prop, index) => {
@@ -3105,6 +3175,10 @@ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}
3105
3175
  estreeTypedParent.type === 'InlineComponent'
3106
3176
  ? element
3107
3177
  : undefined);
3178
+ if (!element) {
3179
+ // root snippet -> move to instance script
3180
+ rootSnippets.push([node.start, node.end]);
3181
+ }
3108
3182
  break;
3109
3183
  case 'MustacheTag':
3110
3184
  handleMustacheTag(str, node, parent);
@@ -3129,6 +3203,9 @@ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}
3129
3203
  else {
3130
3204
  element = new InlineComponent(str, node);
3131
3205
  }
3206
+ if (options.svelte5Plus) {
3207
+ handleImplicitChildren(node, element);
3208
+ }
3132
3209
  break;
3133
3210
  case 'Element':
3134
3211
  case 'Options':
@@ -3233,6 +3310,7 @@ function convertHtmlxToJsx(str, ast, onWalk = null, onLeave = null, options = {}
3233
3310
  }
3234
3311
  }
3235
3312
  });
3313
+ return rootSnippets;
3236
3314
  }
3237
3315
 
3238
3316
  /**
@@ -4235,10 +4313,11 @@ function is$$PropsDeclaration(node) {
4235
4313
  return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Props';
4236
4314
  }
4237
4315
  class ExportedNames {
4238
- constructor(str, astOffset, basename) {
4316
+ constructor(str, astOffset, basename, isTsFile) {
4239
4317
  this.str = str;
4240
4318
  this.astOffset = astOffset;
4241
4319
  this.basename = basename;
4320
+ this.isTsFile = isTsFile;
4242
4321
  /**
4243
4322
  * Uses the `$$Props` type
4244
4323
  */
@@ -4329,16 +4408,89 @@ class ExportedNames {
4329
4408
  this.$props.generic = node.initializer.typeArguments[0].getText();
4330
4409
  }
4331
4410
  else {
4332
- const nodeText = node.getFullText();
4333
- let comments = (_b = ts
4334
- .getLeadingCommentRanges(nodeText, 0)) === null || _b === void 0 ? void 0 : _b.map((c) => nodeText.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4335
- if (!comments) {
4336
- const parentText = node.parent.getFullText();
4337
- comments = (_c = ts
4338
- .getLeadingCommentRanges(parentText, node.parent.pos)) === null || _c === void 0 ? void 0 : _c.map((c) => parentText.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4411
+ if (!this.isTsFile) {
4412
+ const text = node.getSourceFile().getFullText();
4413
+ let comments = (_b = ts
4414
+ .getLeadingCommentRanges(text, node.pos)) === null || _b === void 0 ? void 0 : _b.map((c) => text.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4415
+ if (!comments) {
4416
+ comments = (_c = ts
4417
+ .getLeadingCommentRanges(text, node.parent.pos)) === null || _c === void 0 ? void 0 : _c.map((c) => text.substring(c.pos, c.end)).find((c) => c.includes('@type'));
4418
+ }
4419
+ // We don't bother extracting the type, we just use the comment as-is
4420
+ this.$props.comment = comments || '';
4421
+ }
4422
+ if (this.$props.comment) {
4423
+ return;
4424
+ }
4425
+ if (internalHelpers.isKitRouteFile(this.basename)) {
4426
+ const kitType = this.basename.includes('layout')
4427
+ ? `{ data: import('./$types.js').LayoutData, form: import('./$types.js').ActionData, children: import('svelte').Snippet }`
4428
+ : `{ data: import('./$types.js').PageData, form: import('./$types.js').ActionData }`;
4429
+ if (this.isTsFile) {
4430
+ this.$props.generic = kitType;
4431
+ preprendStr(this.str, node.initializer.expression.end + this.astOffset, surroundWithIgnoreComments(`<${kitType}>`));
4432
+ }
4433
+ else {
4434
+ this.$props.comment = `/** @type {${kitType}} */`;
4435
+ preprendStr(this.str, node.pos + this.astOffset, this.$props.comment);
4436
+ }
4437
+ }
4438
+ else {
4439
+ // Do a best-effort to extract the props from the object literal
4440
+ let propsStr = '';
4441
+ let withUnknown = false;
4442
+ let props = [];
4443
+ if (ts.isObjectBindingPattern(node.name)) {
4444
+ for (const element of node.name.elements) {
4445
+ if (!ts.isIdentifier(element.name) || !!element.dotDotDotToken) {
4446
+ withUnknown = true;
4447
+ }
4448
+ else {
4449
+ if (element.initializer) {
4450
+ const type = ts.isAsExpression(element.initializer)
4451
+ ? element.initializer.type.getText()
4452
+ : ts.isStringLiteral(element.initializer)
4453
+ ? 'string'
4454
+ : ts.isNumericLiteral(element.initializer)
4455
+ ? 'number'
4456
+ : element.initializer.kind === ts.SyntaxKind.TrueKeyword ||
4457
+ element.initializer.kind === ts.SyntaxKind.FalseKeyword
4458
+ ? 'boolean'
4459
+ : ts.isIdentifier(element.initializer)
4460
+ ? `typeof ${element.initializer.text}`
4461
+ : 'unknown';
4462
+ props.push(`${element.name.text}?: ${type}`);
4463
+ }
4464
+ else {
4465
+ props.push(`${element.name.text}: unknown`);
4466
+ }
4467
+ }
4468
+ }
4469
+ }
4470
+ if (props.length > 0) {
4471
+ propsStr =
4472
+ `{ ${props.join(', ')} }` +
4473
+ (withUnknown ? ' & Record<string, unknown>' : '');
4474
+ }
4475
+ else if (withUnknown) {
4476
+ propsStr = 'Record<string, unknown>';
4477
+ }
4478
+ else {
4479
+ propsStr = 'Record<string, never>';
4480
+ }
4481
+ if (this.isTsFile) {
4482
+ this.$props.generic = propsStr;
4483
+ if (props.length > 0 || withUnknown) {
4484
+ preprendStr(this.str, node.initializer.expression.end + this.astOffset, surroundWithIgnoreComments(`<${propsStr}>`));
4485
+ }
4486
+ }
4487
+ else {
4488
+ this.$props.comment = `/** @type {${propsStr}} */`;
4489
+ if (props.length > 0 || withUnknown) {
4490
+ preprendStr(this.str, node.pos + this.astOffset, this.$props.comment);
4491
+ }
4492
+ }
4339
4493
  }
4340
- // We don't bother extracting the type, we just use the comment as-is
4341
- this.$props.comment = comments || '';
4342
4494
  }
4343
4495
  }
4344
4496
  removeExport(start, end) {
@@ -4491,6 +4643,14 @@ class ExportedNames {
4491
4643
  });
4492
4644
  }
4493
4645
  }
4646
+ addImplicitChildrenExport(hasAttributes) {
4647
+ if (this.exports.has('children'))
4648
+ return;
4649
+ this.exports.set('children', {
4650
+ isLet: true,
4651
+ implicitChildren: hasAttributes ? 'attributes' : 'empty'
4652
+ });
4653
+ }
4494
4654
  /**
4495
4655
  * Adds export to map
4496
4656
  */
@@ -4550,7 +4710,7 @@ class ExportedNames {
4550
4710
  * @param isTsFile Whether this is a TypeScript file or not.
4551
4711
  * @param uses$$propsOr$$restProps whether the file references the $$props or $$restProps variable
4552
4712
  */
4553
- createPropsStr(isTsFile, uses$$propsOr$$restProps) {
4713
+ createPropsStr(uses$$propsOr$$restProps) {
4554
4714
  const names = Array.from(this.exports.entries());
4555
4715
  if (this.$props.generic) {
4556
4716
  const others = names.filter(([, { isLet }]) => !isLet);
@@ -4586,11 +4746,11 @@ class ExportedNames {
4586
4746
  }
4587
4747
  if (names.length === 0 && !uses$$propsOr$$restProps) {
4588
4748
  // Necessary, because {} roughly equals to any
4589
- return isTsFile
4749
+ return this.isTsFile
4590
4750
  ? '{} as Record<string, never>'
4591
4751
  : '/** @type {Record<string, never>} */ ({})';
4592
4752
  }
4593
- const dontAddTypeDef = !isTsFile || names.every(([_, value]) => !value.type && value.required);
4753
+ const dontAddTypeDef = !this.isTsFile || names.every(([_, value]) => !value.type && value.required);
4594
4754
  const returnElements = this.createReturnElements(names, dontAddTypeDef);
4595
4755
  if (dontAddTypeDef) {
4596
4756
  // Only `typeof` exports -> omit the `as {...}` completely.
@@ -4602,12 +4762,22 @@ class ExportedNames {
4602
4762
  }
4603
4763
  createReturnElements(names, dontAddTypeDef) {
4604
4764
  return names.map(([key, value]) => {
4765
+ if (value.implicitChildren) {
4766
+ return `children?: ${value.implicitChildren === 'empty'
4767
+ ? '__sveltets_2_snippet()'
4768
+ : '$$implicit_children'}`;
4769
+ }
4605
4770
  // Important to not use shorthand props for rename functionality
4606
4771
  return `${dontAddTypeDef && value.doc ? `\n${value.doc}` : ''}${value.identifierText || key}: ${key}`;
4607
4772
  });
4608
4773
  }
4609
4774
  createReturnElementsType(names) {
4610
4775
  return names.map(([key, value]) => {
4776
+ if (value.implicitChildren) {
4777
+ return `children?: ${value.implicitChildren === 'empty'
4778
+ ? `import('svelte').Snippet`
4779
+ : 'typeof $$implicit_children'}`;
4780
+ }
4611
4781
  const identifier = `${value.doc ? `\n${value.doc}` : ''}${value.identifierText || key}${value.required ? '' : '?'}`;
4612
4782
  if (!value.type) {
4613
4783
  return `${identifier}: typeof ${key}`;
@@ -4623,6 +4793,9 @@ class ExportedNames {
4623
4793
  getExportsMap() {
4624
4794
  return this.exports;
4625
4795
  }
4796
+ uses$propsRune() {
4797
+ return !!this.$props.generic || !!this.$props.comment;
4798
+ }
4626
4799
  }
4627
4800
 
4628
4801
  function isMember(parent, prop) {
@@ -5568,12 +5741,12 @@ class InterfacesAndTypes {
5568
5741
  }
5569
5742
  }
5570
5743
 
5571
- function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, hasModuleScript, basename) {
5744
+ function processInstanceScriptContent(str, script, events, implicitStoreValues, mode, hasModuleScript, isTSFile, basename) {
5572
5745
  const htmlx = str.original;
5573
5746
  const scriptContent = htmlx.substring(script.content.start, script.content.end);
5574
5747
  const tsAst = ts.createSourceFile('component.ts.svelte', scriptContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
5575
5748
  const astOffset = script.content.start;
5576
- const exportedNames = new ExportedNames(str, astOffset, basename);
5749
+ const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile);
5577
5750
  const generics = new Generics(str, astOffset, script);
5578
5751
  const interfacesAndTypes = new InterfacesAndTypes();
5579
5752
  const implicitTopLevelNames = new ImplicitTopLevelNames(str, astOffset);
@@ -6022,7 +6195,7 @@ function classNameFromFilename(filename, appendSuffix) {
6022
6195
  }
6023
6196
  }
6024
6197
 
6025
- function createRenderFunction({ str, scriptTag, scriptDestination, slots, events, exportedNames, isTsFile, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, mode }) {
6198
+ function createRenderFunction({ str, scriptTag, scriptDestination, rootSnippets, slots, events, exportedNames, uses$$props, uses$$restProps, uses$$slots, uses$$SlotsInterface, generics, svelte5Plus, mode }) {
6026
6199
  const htmlx = str.original;
6027
6200
  let propsDecl = '';
6028
6201
  if (uses$$props) {
@@ -6069,6 +6242,9 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6069
6242
  str.overwrite(scriptTag.start + 1, scriptTagEnd, `function render${generics.toDefinitionString(true)}() {${propsDecl}\n`);
6070
6243
  }
6071
6244
  const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
6245
+ for (const rootSnippet of rootSnippets) {
6246
+ str.move(rootSnippet[0], rootSnippet[1], scriptEndTagStart);
6247
+ }
6072
6248
  // wrap template with callback
6073
6249
  str.overwrite(scriptEndTagStart, scriptTag.end, `${slotsDeclaration};\nasync () => {`, {
6074
6250
  contentOnly: true
@@ -6082,22 +6258,32 @@ function createRenderFunction({ str, scriptTag, scriptDestination, slots, events
6082
6258
  : '{' +
6083
6259
  Array.from(slots.entries())
6084
6260
  .map(([name, attrs]) => {
6085
- const attrsAsString = Array.from(attrs.entries())
6086
- .map(([exportName, expr]) => exportName.startsWith('__spread__')
6087
- ? `...${expr}`
6088
- : `${exportName}:${expr}`)
6089
- .join(', ');
6090
- return `'${name}': {${attrsAsString}}`;
6261
+ return `'${name}': {${slotAttributesToString(attrs)}}`;
6091
6262
  })
6092
6263
  .join(', ') +
6093
6264
  '}';
6094
- const returnString = `\nreturn { props: ${exportedNames.createPropsStr(isTsFile, uses$$props || uses$$restProps)}` +
6265
+ const needsImplicitChildrenProp = svelte5Plus &&
6266
+ !exportedNames.uses$propsRune() &&
6267
+ slots.has('default') &&
6268
+ !exportedNames.getExportsMap().has('default');
6269
+ if (needsImplicitChildrenProp) {
6270
+ exportedNames.addImplicitChildrenExport(slots.get('default').size > 0);
6271
+ }
6272
+ const returnString = `${needsImplicitChildrenProp && slots.get('default').size > 0
6273
+ ? `\nlet $$implicit_children = __sveltets_2_snippet({${slotAttributesToString(slots.get('default'))}});`
6274
+ : ''}` +
6275
+ `\nreturn { props: ${exportedNames.createPropsStr(uses$$props || uses$$restProps)}` +
6095
6276
  `, slots: ${slotsAsDef}` +
6096
6277
  `, events: ${events.toDefString()} }}`;
6097
6278
  // wrap template with callback
6098
6279
  str.append('};');
6099
6280
  str.append(returnString);
6100
6281
  }
6282
+ function slotAttributesToString(attrs) {
6283
+ return Array.from(attrs.entries())
6284
+ .map(([exportName, expr]) => exportName.startsWith('__spread__') ? `...${expr}` : `${exportName}:${expr}`)
6285
+ .join(', ');
6286
+ }
6101
6287
 
6102
6288
  function processSvelteTemplate(str, parse, options) {
6103
6289
  const { htmlxAst, tags } = parseHtmlx(str.original, parse, options);
@@ -6283,9 +6469,10 @@ function processSvelteTemplate(str, parse, options) {
6283
6469
  break;
6284
6470
  }
6285
6471
  };
6286
- convertHtmlxToJsx(str, htmlxAst, onHtmlxWalk, onHtmlxLeave, {
6472
+ const rootSnippets = convertHtmlxToJsx(str, htmlxAst, onHtmlxWalk, onHtmlxLeave, {
6287
6473
  preserveAttributeCase: (options === null || options === void 0 ? void 0 : options.namespace) == 'foreign',
6288
- typingsNamespace: options.typingsNamespace
6474
+ typingsNamespace: options.typingsNamespace,
6475
+ svelte5Plus: options.svelte5Plus
6289
6476
  });
6290
6477
  // resolve scripts
6291
6478
  const { scriptTag, moduleScriptTag } = scripts.getTopLevelScriptTags();
@@ -6298,6 +6485,7 @@ function processSvelteTemplate(str, parse, options) {
6298
6485
  htmlAst: htmlxAst,
6299
6486
  moduleScriptTag,
6300
6487
  scriptTag,
6488
+ rootSnippets,
6301
6489
  slots: slotHandler.getSlotDef(),
6302
6490
  events: new ComponentEvents(eventHandler, tags.some((tag) => { var _a; return (_a = tag.attributes) === null || _a === void 0 ? void 0 : _a.some((a) => a.name === 'strictEvents'); }), str),
6303
6491
  uses$$props,
@@ -6310,10 +6498,15 @@ function processSvelteTemplate(str, parse, options) {
6310
6498
  }
6311
6499
  function svelte2tsx(svelte, options = { parse }) {
6312
6500
  options.mode = options.mode || 'ts';
6501
+ options.version = options.version || VERSION;
6313
6502
  const str = new MagicString(svelte);
6314
6503
  const basename = path__default.basename(options.filename || '');
6504
+ const svelte5Plus = Number(options.version[0]) > 4;
6315
6505
  // process the htmlx as a svelte template
6316
- let { htmlAst, moduleScriptTag, scriptTag, slots, uses$$props, uses$$slots, uses$$restProps, events, componentDocumentation, resolvedStores, usesAccessors } = processSvelteTemplate(str, options.parse || parse, options);
6506
+ let { htmlAst, moduleScriptTag, scriptTag, rootSnippets, slots, uses$$props, uses$$slots, uses$$restProps, events, componentDocumentation, resolvedStores, usesAccessors } = processSvelteTemplate(str, options.parse || parse, {
6507
+ ...options,
6508
+ svelte5Plus
6509
+ });
6317
6510
  /* Rearrange the script tags so that module is first, and instance second followed finally by the template
6318
6511
  * This is a bit convoluted due to some trouble I had with magic string. A simple str.move(start,end,0) for each script wasn't enough
6319
6512
  * since if the module script was already at 0, it wouldn't move (which is fine) but would mean the order would be swapped when the script tag tried to move to 0
@@ -6335,7 +6528,7 @@ function svelte2tsx(svelte, options = { parse }) {
6335
6528
  : instanceScriptTarget;
6336
6529
  const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart);
6337
6530
  //move the instance script and process the content
6338
- let exportedNames = new ExportedNames(str, 0, basename);
6531
+ let exportedNames = new ExportedNames(str, 0, basename, options === null || options === void 0 ? void 0 : options.isTsFile);
6339
6532
  let generics = new Generics(str, 0, { attributes: [] });
6340
6533
  let uses$$SlotsInterface = false;
6341
6534
  if (scriptTag) {
@@ -6344,7 +6537,7 @@ function svelte2tsx(svelte, options = { parse }) {
6344
6537
  str.move(scriptTag.start, scriptTag.end, instanceScriptTarget);
6345
6538
  }
6346
6539
  const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues, options.mode,
6347
- /**hasModuleScripts */ !!moduleScriptTag, basename);
6540
+ /**hasModuleScripts */ !!moduleScriptTag, options === null || options === void 0 ? void 0 : options.isTsFile, basename);
6348
6541
  uses$$props = uses$$props || res.uses$$props;
6349
6542
  uses$$restProps = uses$$restProps || res.uses$$restProps;
6350
6543
  uses$$slots = uses$$slots || res.uses$$slots;
@@ -6355,15 +6548,16 @@ function svelte2tsx(svelte, options = { parse }) {
6355
6548
  str,
6356
6549
  scriptTag,
6357
6550
  scriptDestination: instanceScriptTarget,
6551
+ rootSnippets,
6358
6552
  slots,
6359
6553
  events,
6360
6554
  exportedNames,
6361
- isTsFile: options === null || options === void 0 ? void 0 : options.isTsFile,
6362
6555
  uses$$props,
6363
6556
  uses$$restProps,
6364
6557
  uses$$slots,
6365
6558
  uses$$SlotsInterface,
6366
6559
  generics,
6560
+ svelte5Plus,
6367
6561
  mode: options.mode
6368
6562
  });
6369
6563
  // we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items
@@ -6470,13 +6664,13 @@ function loadTsconfig(config, svelteMap) {
6470
6664
  return {
6471
6665
  options: {
6472
6666
  ...options,
6473
- noEmit: false,
6667
+ noEmit: false, // Set to true in case of jsconfig, force false, else nothing is emitted
6474
6668
  moduleResolution:
6475
6669
  // NodeJS: up to 4.9, Node10: since 5.0
6476
- (_a = ts.ModuleResolutionKind.NodeJs) !== null && _a !== void 0 ? _a : ts.ModuleResolutionKind.Node10,
6477
- declaration: true,
6478
- emitDeclarationOnly: true,
6479
- declarationDir: config.declarationDir,
6670
+ (_a = ts.ModuleResolutionKind.NodeJs) !== null && _a !== void 0 ? _a : ts.ModuleResolutionKind.Node10, // Classic if not set, which gives wrong results
6671
+ declaration: true, // Needed for d.ts file generation
6672
+ emitDeclarationOnly: true, // We only want d.ts file generation
6673
+ declarationDir: config.declarationDir, // Where to put the declarations
6480
6674
  allowNonTsExtensions: true
6481
6675
  },
6482
6676
  filenames
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte2tsx",
3
- "version": "0.6.25",
3
+ "version": "0.6.27",
4
4
  "description": "Convert Svelte components to TSX for type checking",
5
5
  "author": "David Pershouse",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "svelte": "~3.57.0",
41
41
  "tiny-glob": "^0.2.6",
42
42
  "tslib": "^2.4.0",
43
- "typescript": "^5.2.2"
43
+ "typescript": "^5.3.2"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0",
@@ -145,6 +145,11 @@ declare function __sveltets_2_nonNullable<T>(type: T): NonNullable<T>;
145
145
 
146
146
  declare function __sveltets_2_cssProp(prop: Record<string, any>): {};
147
147
 
148
+ // @ts-ignore Svelte v3/v4 don't have this
149
+ declare function __sveltets_2_ensureSnippet(val: ReturnType<import('svelte').Snippet>): any;
150
+ // @ts-ignore Svelte v3/v4 don't have this
151
+ declare function __sveltets_2_snippet<T = void>(t: T): import('svelte').Snippet<T>;
152
+
148
153
  /** @internal PRIVATE API, DO NOT USE */
149
154
  type __sveltets_2_SvelteAnimationReturnType = {
150
155
  delay?: number,