svelte2tsx 0.7.29 → 0.7.31

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 (3) hide show
  1. package/index.js +116 -39
  2. package/index.mjs +116 -39
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -1673,10 +1673,10 @@ function parseHtmlx(htmlx, parse, options) {
1673
1673
  const verbatimElements = findVerbatimElements(htmlx);
1674
1674
  const deconstructed = blankVerbatimContent(htmlx, verbatimElements);
1675
1675
  //extract the html content parsed as htmlx this excludes our script and style tags
1676
- const parsingCode = options.emitOnTemplateError
1676
+ const parsingCode = options.emitOnTemplateError && !options.svelte5Plus
1677
1677
  ? blankPossiblyErrorOperatorOrPropertyAccess(deconstructed)
1678
1678
  : deconstructed;
1679
- const htmlxAst = parse(parsingCode).html;
1679
+ const htmlxAst = parse(parsingCode, options.svelte5Plus ? { loose: options.emitOnTemplateError } : undefined).html;
1680
1680
  //restore our script and style tags as nodes to maintain validity with HTMLx
1681
1681
  for (const s of verbatimElements) {
1682
1682
  htmlxAst.children.push(s);
@@ -1760,8 +1760,7 @@ function handleActionDirective(attr, element) {
1760
1760
  * you may need to find a different way because MagicString does not allow us to move a range
1761
1761
  * that goes from `start` to `end` to the `end` position.
1762
1762
  */
1763
- function transform(str, start, end, _xxx, // TODO
1764
- transformations) {
1763
+ function transform(str, start, end, transformations) {
1765
1764
  var _a, _b;
1766
1765
  const moves = [];
1767
1766
  let appendPosition = end;
@@ -1853,6 +1852,10 @@ transformations) {
1853
1852
  }
1854
1853
  }
1855
1854
  for (let i = deletePos; i < moves.length; i++) {
1855
+ // Can happen when there's not enough space left at the end of an unfininished element/component tag.
1856
+ // Better to leave potentially slightly disarranged code than fail loudly
1857
+ if (moves[i][1] >= end && moves[i][0] <= end)
1858
+ break;
1856
1859
  str.move(moves[i][0], moves[i][1], end);
1857
1860
  }
1858
1861
  }
@@ -1936,6 +1939,22 @@ function isTypescriptNode(node) {
1936
1939
  node.type === 'TSSatisfiesExpression' ||
1937
1940
  node.type === 'TSNonNullExpression');
1938
1941
  }
1942
+ /**
1943
+ * Returns `true` if the given block is implicitly closed, which could be the case in loose parsing mode.
1944
+ * E.g.:
1945
+ * ```html
1946
+ * <div>
1947
+ * {#if x}
1948
+ * </div>
1949
+ * ```
1950
+ * @param end
1951
+ * @param block
1952
+ * @returns
1953
+ */
1954
+ function isImplicitlyClosedBlock(end, block) {
1955
+ var _a, _b;
1956
+ return end < ((_b = (_a = block.children[block.children.length - 1]) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : block.expression.end);
1957
+ }
1939
1958
 
1940
1959
  /**
1941
1960
  * animate:xxx(yyy) ---> __sveltets_2_ensureAnimation(xxx(svelte.mapElementTag('..'),__sveltets_2_AnimationMove,(yyy)));
@@ -2124,7 +2143,7 @@ class Element {
2124
2143
  this.endTransformation.push('}');
2125
2144
  }
2126
2145
  if (this.isSelfclosing) {
2127
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2146
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2128
2147
  // Named slot transformations go first inside a outer block scope because
2129
2148
  // <div let:xx {x} /> means "use the x of let:x", and without a separate
2130
2149
  // block scope this would give a "used before defined" error
@@ -2137,7 +2156,7 @@ class Element {
2137
2156
  ]);
2138
2157
  }
2139
2158
  else {
2140
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2159
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2141
2160
  ...slotLetTransformation,
2142
2161
  ...this.actionsTransformation,
2143
2162
  ...this.getStartTransformation(),
@@ -2149,7 +2168,7 @@ class Element {
2149
2168
  .lastIndexOf(`</${this.node.name}`);
2150
2169
  // tagEndIdx === -1 happens in situations of unclosed tags like `<p>fooo <p>anothertag</p>`
2151
2170
  const endStart = tagEndIdx === -1 ? this.node.end : tagEndIdx + this.node.start;
2152
- transform(this.str, endStart, this.node.end, this.node.end, this.endTransformation);
2171
+ transform(this.str, endStart, this.node.end, this.endTransformation);
2153
2172
  }
2154
2173
  }
2155
2174
  getStartTransformation() {
@@ -2406,7 +2425,7 @@ class InlineComponent {
2406
2425
  : '';
2407
2426
  if (this.isSelfclosing) {
2408
2427
  this.endTransformation.push('}');
2409
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2428
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2410
2429
  // Named slot transformations go first inside a outer block scope because
2411
2430
  // <Comp let:xx {x} /> means "use the x of let:x", and without a separate
2412
2431
  // block scope this would give a "used before defined" error
@@ -2421,15 +2440,23 @@ class InlineComponent {
2421
2440
  ]);
2422
2441
  }
2423
2442
  else {
2424
- const endStart = this.str.original
2443
+ let endStart = this.str.original
2425
2444
  .substring(this.node.start, this.node.end)
2426
- .lastIndexOf(`</${this.node.name}`) + this.node.start;
2427
- if (!this.node.name.startsWith('svelte:')) {
2445
+ .lastIndexOf(`</${this.node.name}`);
2446
+ if (endStart === -1) {
2447
+ // Can happen in loose parsing mode when there's no closing tag
2448
+ endStart = this.node.end;
2449
+ this.startTagEnd = this.node.end - 1;
2450
+ }
2451
+ else {
2452
+ endStart += this.node.start;
2453
+ }
2454
+ if (!this.node.name.startsWith('svelte:') && endStart !== this.node.end) {
2428
2455
  // Ensure the end tag is mapped, too. </Component> -> Component}
2429
2456
  this.endTransformation.push([endStart + 2, endStart + this.node.name.length + 2]);
2430
2457
  }
2431
2458
  this.endTransformation.push('}');
2432
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2459
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2433
2460
  // See comment above why this goes first
2434
2461
  ...namedSlotLetTransformation,
2435
2462
  ...this.startTransformation,
@@ -2439,7 +2466,7 @@ class InlineComponent {
2439
2466
  snippetPropVariablesDeclaration,
2440
2467
  ...defaultSlotLetTransformation
2441
2468
  ]);
2442
- transform(this.str, endStart, this.node.end, this.node.end, this.endTransformation);
2469
+ transform(this.str, endStart, this.node.end, this.endTransformation);
2443
2470
  }
2444
2471
  }
2445
2472
  computeStartTagEnd() {
@@ -2568,7 +2595,14 @@ function handleAttribute(str, attr, parent, preserveCase, svelte5Plus, element)
2568
2595
  const attributeName = [];
2569
2596
  if (attributeValueIsOfType(attr.value, 'AttributeShorthand')) {
2570
2597
  // For the attribute shorthand, the name will be the mapped part
2571
- addAttribute([[attr.value[0].start, attr.value[0].end]]);
2598
+ let [start, end] = [attr.value[0].start, attr.value[0].end];
2599
+ if (start === end) {
2600
+ // Loose parsing mode, we have an empty attribute value, e.g. {}
2601
+ // For proper intellisense we need to make this a non-empty expression.
2602
+ start--;
2603
+ str.overwrite(start, end, ' ', { contentOnly: true });
2604
+ }
2605
+ addAttribute([[start, end]]);
2572
2606
  return;
2573
2607
  }
2574
2608
  else {
@@ -2640,7 +2674,14 @@ function handleAttribute(str, attr, parent, preserveCase, svelte5Plus, element)
2640
2674
  addAttribute(attributeName, attributeValue);
2641
2675
  }
2642
2676
  else if (attrVal.type == 'MustacheTag') {
2643
- attributeValue.push(rangeWithTrailingPropertyAccess(str.original, attrVal.expression));
2677
+ let [start, end] = rangeWithTrailingPropertyAccess(str.original, attrVal.expression);
2678
+ if (start === end) {
2679
+ // Loose parsing mode, we have an empty attribute value, e.g. attr={}
2680
+ // For proper intellisense we need to make this a non-empty expression.
2681
+ start--;
2682
+ str.overwrite(start, end, ' ', { contentOnly: true });
2683
+ }
2684
+ attributeValue.push([start, end]);
2644
2685
  addAttribute(attributeName, attributeValue);
2645
2686
  }
2646
2687
  return;
@@ -2726,7 +2767,7 @@ function handleAwait(str, awaitBlock) {
2726
2767
  transforms.push('}');
2727
2768
  }
2728
2769
  transforms.push('}');
2729
- transform(str, awaitBlock.start, awaitBlock.end, awaitBlock.end, transforms);
2770
+ transform(str, awaitBlock.start, awaitBlock.end, transforms);
2730
2771
  }
2731
2772
 
2732
2773
  /**
@@ -2919,17 +2960,18 @@ function handleDebug(str, debugBlock) {
2919
2960
  * `ensureArray` will error that there are more args than expected
2920
2961
  */
2921
2962
  function handleEach(str, eachBlock) {
2922
- var _a;
2923
- const startEnd = str.original.indexOf('}', ((_a = eachBlock.key) === null || _a === void 0 ? void 0 : _a.end) || eachBlock.context.end) + 1;
2963
+ var _a, _b, _c;
2964
+ const startEnd = str.original.indexOf('}', ((_a = eachBlock.key) === null || _a === void 0 ? void 0 : _a.end) || ((_b = eachBlock.context) === null || _b === void 0 ? void 0 : _b.end) || eachBlock.expression.end) + 1;
2924
2965
  let transforms;
2925
2966
  // {#each true, [1,2]} is valid but for (const x of true, [1,2]) is not if not wrapped with braces
2926
2967
  const containsComma = str.original
2927
2968
  .substring(eachBlock.expression.start, eachBlock.expression.end)
2928
2969
  .includes(',');
2929
2970
  const expressionEnd = getEnd(eachBlock.expression);
2930
- const contextEnd = getEnd(eachBlock.context);
2931
- const arrayAndItemVarTheSame = str.original.substring(eachBlock.expression.start, expressionEnd) ===
2932
- str.original.substring(eachBlock.context.start, contextEnd);
2971
+ const contextEnd = eachBlock.context && getEnd(eachBlock.context);
2972
+ const arrayAndItemVarTheSame = !!eachBlock.context &&
2973
+ str.original.substring(eachBlock.expression.start, expressionEnd) ===
2974
+ str.original.substring(eachBlock.context.start, contextEnd);
2933
2975
  if (arrayAndItemVarTheSame) {
2934
2976
  transforms = [
2935
2977
  `{ const $$_each = __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
@@ -2942,21 +2984,21 @@ function handleEach(str, eachBlock) {
2942
2984
  else {
2943
2985
  transforms = [
2944
2986
  'for(let ',
2945
- [eachBlock.context.start, contextEnd],
2987
+ eachBlock.context ? [eachBlock.context.start, contextEnd] : '$$each_item',
2946
2988
  ` of __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
2947
2989
  [eachBlock.expression.start, eachBlock.expression.end],
2948
- `${containsComma ? ')' : ''})){`
2990
+ `${containsComma ? ')' : ''})){${eachBlock.context ? '' : '$$each_item;'}`
2949
2991
  ];
2950
2992
  }
2951
2993
  if (eachBlock.index) {
2952
- const indexStart = str.original.indexOf(eachBlock.index, eachBlock.context.end);
2994
+ const indexStart = str.original.indexOf(eachBlock.index, ((_c = eachBlock.context) === null || _c === void 0 ? void 0 : _c.end) || eachBlock.expression.end);
2953
2995
  const indexEnd = indexStart + eachBlock.index.length;
2954
2996
  transforms.push('let ', [indexStart, indexEnd], ' = 1;');
2955
2997
  }
2956
2998
  if (eachBlock.key) {
2957
2999
  transforms.push([eachBlock.key.start, eachBlock.key.end], ';');
2958
3000
  }
2959
- transform(str, eachBlock.start, startEnd, startEnd, transforms);
3001
+ transform(str, eachBlock.start, startEnd, transforms);
2960
3002
  const endEach = str.original.lastIndexOf('{', eachBlock.end - 1);
2961
3003
  // {/each} -> } or {:else} -> }
2962
3004
  if (eachBlock.else) {
@@ -2965,12 +3007,20 @@ function handleEach(str, eachBlock) {
2965
3007
  str.overwrite(elseStart, elseEnd + 1, '}' + (arrayAndItemVarTheSame ? '}' : ''), {
2966
3008
  contentOnly: true
2967
3009
  });
2968
- str.remove(endEach, eachBlock.end);
3010
+ if (!isImplicitlyClosedBlock(endEach, eachBlock)) {
3011
+ str.remove(endEach, eachBlock.end);
3012
+ }
2969
3013
  }
2970
3014
  else {
2971
- str.overwrite(endEach, eachBlock.end, '}' + (arrayAndItemVarTheSame ? '}' : ''), {
2972
- contentOnly: true
2973
- });
3015
+ const closing = '}' + (arrayAndItemVarTheSame ? '}' : '');
3016
+ if (isImplicitlyClosedBlock(endEach, eachBlock)) {
3017
+ str.prependLeft(eachBlock.end, closing);
3018
+ }
3019
+ else {
3020
+ str.overwrite(endEach, eachBlock.end, closing, {
3021
+ contentOnly: true
3022
+ });
3023
+ }
2974
3024
  }
2975
3025
  }
2976
3026
 
@@ -3014,9 +3064,14 @@ function handleIf(str, ifBlock) {
3014
3064
  const expressionEnd = withTrailingPropertyAccess(str.original, ifBlock.expression.end);
3015
3065
  const end = str.original.indexOf('}', expressionEnd);
3016
3066
  str.overwrite(expressionEnd, end + 1, '){');
3017
- // {/if} -> }
3018
3067
  const endif = str.original.lastIndexOf('{', ifBlock.end - 1);
3019
- str.overwrite(endif, ifBlock.end, '}');
3068
+ if (isImplicitlyClosedBlock(endif, ifBlock)) {
3069
+ str.prependLeft(ifBlock.end, '}');
3070
+ }
3071
+ else {
3072
+ // {/if} -> }
3073
+ str.overwrite(endif, ifBlock.end, '}');
3074
+ }
3020
3075
  }
3021
3076
  /**
3022
3077
  * {:else} ---> } else {
@@ -3046,7 +3101,9 @@ function handleKey(str, keyBlock) {
3046
3101
  str.overwrite(expressionEnd, end + 1, '; ');
3047
3102
  // {/key} ->
3048
3103
  const endKey = str.original.lastIndexOf('{', keyBlock.end - 1);
3049
- str.overwrite(endKey, keyBlock.end, '', { contentOnly: true });
3104
+ if (!isImplicitlyClosedBlock(endKey, keyBlock)) {
3105
+ str.overwrite(endKey, keyBlock.end, '', { contentOnly: true });
3106
+ }
3050
3107
  }
3051
3108
 
3052
3109
  /**
@@ -3244,9 +3301,14 @@ function handleSnippet(str, snippetBlock, component) {
3244
3301
  const afterSnippet = isImplicitProp
3245
3302
  ? `};return __sveltets_2_any(0)}`
3246
3303
  : `};return __sveltets_2_any(0)};`;
3247
- str.overwrite(endSnippet, snippetBlock.end, afterSnippet, {
3248
- contentOnly: true
3249
- });
3304
+ if (isImplicitlyClosedBlock(endSnippet, snippetBlock)) {
3305
+ str.prependLeft(snippetBlock.end, afterSnippet);
3306
+ }
3307
+ else {
3308
+ str.overwrite(endSnippet, snippetBlock.end, afterSnippet, {
3309
+ contentOnly: true
3310
+ });
3311
+ }
3250
3312
  const lastParameter = (_a = snippetBlock.parameters) === null || _a === void 0 ? void 0 : _a.at(-1);
3251
3313
  const startEnd = str.original.indexOf('}', (_d = (_c = (_b = lastParameter === null || lastParameter === void 0 ? void 0 : lastParameter.typeAnnotation) === null || _b === void 0 ? void 0 : _b.end) !== null && _c !== void 0 ? _c : lastParameter === null || lastParameter === void 0 ? void 0 : lastParameter.end) !== null && _d !== void 0 ? _d : snippetBlock.expression.end) + 1;
3252
3314
  let parameters;
@@ -3259,7 +3321,22 @@ function handleSnippet(str, snippetBlock, component) {
3259
3321
  // inner async function for potential #await blocks
3260
3322
  const afterParameters = ` => { async ()${IGNORE_POSITION_COMMENT} => {`;
3261
3323
  if (isImplicitProp) {
3262
- str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { contentOnly: true });
3324
+ /** Can happen in loose parsing mode, e.g. code is currently `{#snippet }` */
3325
+ const emptyId = snippetBlock.expression.start === snippetBlock.expression.end;
3326
+ if (emptyId) {
3327
+ // Give intellisense a way to map into the right position for implicit prop completion
3328
+ str.overwrite(snippetBlock.start, snippetBlock.expression.start - 1, '', {
3329
+ contentOnly: true
3330
+ });
3331
+ str.overwrite(snippetBlock.expression.start - 1, snippetBlock.expression.start, ' ', {
3332
+ contentOnly: true
3333
+ });
3334
+ }
3335
+ else {
3336
+ str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', {
3337
+ contentOnly: true
3338
+ });
3339
+ }
3263
3340
  const transforms = ['('];
3264
3341
  if (parameters) {
3265
3342
  transforms.push(parameters);
@@ -3275,10 +3352,10 @@ function handleSnippet(str, snippetBlock, component) {
3275
3352
  transforms.push(')' + afterParameters);
3276
3353
  transforms.push([startEnd, snippetBlock.end]);
3277
3354
  if (component instanceof InlineComponent) {
3278
- component.addImplicitSnippetProp([snippetBlock.expression.start, snippetBlock.expression.end], transforms);
3355
+ component.addImplicitSnippetProp([snippetBlock.expression.start - (emptyId ? 1 : 0), snippetBlock.expression.end], transforms);
3279
3356
  }
3280
3357
  else {
3281
- component.addAttribute([[snippetBlock.expression.start, snippetBlock.expression.end]], transforms);
3358
+ component.addAttribute([[snippetBlock.expression.start - (emptyId ? 1 : 0), snippetBlock.expression.end]], transforms);
3282
3359
  }
3283
3360
  }
3284
3361
  else {
@@ -3293,7 +3370,7 @@ function handleSnippet(str, snippetBlock, component) {
3293
3370
  }
3294
3371
  transforms.push(')', surroundWithIgnoreComments(`: ReturnType<import('svelte').Snippet>`), // shows up nicely preserved on hover, other alternatives don't
3295
3372
  afterParameters);
3296
- transform(str, snippetBlock.start, startEnd, startEnd, transforms);
3373
+ transform(str, snippetBlock.start, startEnd, transforms);
3297
3374
  }
3298
3375
  }
3299
3376
  function handleImplicitChildren(componentNode, component) {
package/index.mjs CHANGED
@@ -1653,10 +1653,10 @@ function parseHtmlx(htmlx, parse, options) {
1653
1653
  const verbatimElements = findVerbatimElements(htmlx);
1654
1654
  const deconstructed = blankVerbatimContent(htmlx, verbatimElements);
1655
1655
  //extract the html content parsed as htmlx this excludes our script and style tags
1656
- const parsingCode = options.emitOnTemplateError
1656
+ const parsingCode = options.emitOnTemplateError && !options.svelte5Plus
1657
1657
  ? blankPossiblyErrorOperatorOrPropertyAccess(deconstructed)
1658
1658
  : deconstructed;
1659
- const htmlxAst = parse(parsingCode).html;
1659
+ const htmlxAst = parse(parsingCode, options.svelte5Plus ? { loose: options.emitOnTemplateError } : undefined).html;
1660
1660
  //restore our script and style tags as nodes to maintain validity with HTMLx
1661
1661
  for (const s of verbatimElements) {
1662
1662
  htmlxAst.children.push(s);
@@ -1740,8 +1740,7 @@ function handleActionDirective(attr, element) {
1740
1740
  * you may need to find a different way because MagicString does not allow us to move a range
1741
1741
  * that goes from `start` to `end` to the `end` position.
1742
1742
  */
1743
- function transform(str, start, end, _xxx, // TODO
1744
- transformations) {
1743
+ function transform(str, start, end, transformations) {
1745
1744
  var _a, _b;
1746
1745
  const moves = [];
1747
1746
  let appendPosition = end;
@@ -1833,6 +1832,10 @@ transformations) {
1833
1832
  }
1834
1833
  }
1835
1834
  for (let i = deletePos; i < moves.length; i++) {
1835
+ // Can happen when there's not enough space left at the end of an unfininished element/component tag.
1836
+ // Better to leave potentially slightly disarranged code than fail loudly
1837
+ if (moves[i][1] >= end && moves[i][0] <= end)
1838
+ break;
1836
1839
  str.move(moves[i][0], moves[i][1], end);
1837
1840
  }
1838
1841
  }
@@ -1916,6 +1919,22 @@ function isTypescriptNode(node) {
1916
1919
  node.type === 'TSSatisfiesExpression' ||
1917
1920
  node.type === 'TSNonNullExpression');
1918
1921
  }
1922
+ /**
1923
+ * Returns `true` if the given block is implicitly closed, which could be the case in loose parsing mode.
1924
+ * E.g.:
1925
+ * ```html
1926
+ * <div>
1927
+ * {#if x}
1928
+ * </div>
1929
+ * ```
1930
+ * @param end
1931
+ * @param block
1932
+ * @returns
1933
+ */
1934
+ function isImplicitlyClosedBlock(end, block) {
1935
+ var _a, _b;
1936
+ return end < ((_b = (_a = block.children[block.children.length - 1]) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : block.expression.end);
1937
+ }
1919
1938
 
1920
1939
  /**
1921
1940
  * animate:xxx(yyy) ---> __sveltets_2_ensureAnimation(xxx(svelte.mapElementTag('..'),__sveltets_2_AnimationMove,(yyy)));
@@ -2104,7 +2123,7 @@ class Element {
2104
2123
  this.endTransformation.push('}');
2105
2124
  }
2106
2125
  if (this.isSelfclosing) {
2107
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2126
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2108
2127
  // Named slot transformations go first inside a outer block scope because
2109
2128
  // <div let:xx {x} /> means "use the x of let:x", and without a separate
2110
2129
  // block scope this would give a "used before defined" error
@@ -2117,7 +2136,7 @@ class Element {
2117
2136
  ]);
2118
2137
  }
2119
2138
  else {
2120
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2139
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2121
2140
  ...slotLetTransformation,
2122
2141
  ...this.actionsTransformation,
2123
2142
  ...this.getStartTransformation(),
@@ -2129,7 +2148,7 @@ class Element {
2129
2148
  .lastIndexOf(`</${this.node.name}`);
2130
2149
  // tagEndIdx === -1 happens in situations of unclosed tags like `<p>fooo <p>anothertag</p>`
2131
2150
  const endStart = tagEndIdx === -1 ? this.node.end : tagEndIdx + this.node.start;
2132
- transform(this.str, endStart, this.node.end, this.node.end, this.endTransformation);
2151
+ transform(this.str, endStart, this.node.end, this.endTransformation);
2133
2152
  }
2134
2153
  }
2135
2154
  getStartTransformation() {
@@ -2386,7 +2405,7 @@ class InlineComponent {
2386
2405
  : '';
2387
2406
  if (this.isSelfclosing) {
2388
2407
  this.endTransformation.push('}');
2389
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2408
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2390
2409
  // Named slot transformations go first inside a outer block scope because
2391
2410
  // <Comp let:xx {x} /> means "use the x of let:x", and without a separate
2392
2411
  // block scope this would give a "used before defined" error
@@ -2401,15 +2420,23 @@ class InlineComponent {
2401
2420
  ]);
2402
2421
  }
2403
2422
  else {
2404
- const endStart = this.str.original
2423
+ let endStart = this.str.original
2405
2424
  .substring(this.node.start, this.node.end)
2406
- .lastIndexOf(`</${this.node.name}`) + this.node.start;
2407
- if (!this.node.name.startsWith('svelte:')) {
2425
+ .lastIndexOf(`</${this.node.name}`);
2426
+ if (endStart === -1) {
2427
+ // Can happen in loose parsing mode when there's no closing tag
2428
+ endStart = this.node.end;
2429
+ this.startTagEnd = this.node.end - 1;
2430
+ }
2431
+ else {
2432
+ endStart += this.node.start;
2433
+ }
2434
+ if (!this.node.name.startsWith('svelte:') && endStart !== this.node.end) {
2408
2435
  // Ensure the end tag is mapped, too. </Component> -> Component}
2409
2436
  this.endTransformation.push([endStart + 2, endStart + this.node.name.length + 2]);
2410
2437
  }
2411
2438
  this.endTransformation.push('}');
2412
- transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [
2439
+ transform(this.str, this.startTagStart, this.startTagEnd, [
2413
2440
  // See comment above why this goes first
2414
2441
  ...namedSlotLetTransformation,
2415
2442
  ...this.startTransformation,
@@ -2419,7 +2446,7 @@ class InlineComponent {
2419
2446
  snippetPropVariablesDeclaration,
2420
2447
  ...defaultSlotLetTransformation
2421
2448
  ]);
2422
- transform(this.str, endStart, this.node.end, this.node.end, this.endTransformation);
2449
+ transform(this.str, endStart, this.node.end, this.endTransformation);
2423
2450
  }
2424
2451
  }
2425
2452
  computeStartTagEnd() {
@@ -2548,7 +2575,14 @@ function handleAttribute(str, attr, parent, preserveCase, svelte5Plus, element)
2548
2575
  const attributeName = [];
2549
2576
  if (attributeValueIsOfType(attr.value, 'AttributeShorthand')) {
2550
2577
  // For the attribute shorthand, the name will be the mapped part
2551
- addAttribute([[attr.value[0].start, attr.value[0].end]]);
2578
+ let [start, end] = [attr.value[0].start, attr.value[0].end];
2579
+ if (start === end) {
2580
+ // Loose parsing mode, we have an empty attribute value, e.g. {}
2581
+ // For proper intellisense we need to make this a non-empty expression.
2582
+ start--;
2583
+ str.overwrite(start, end, ' ', { contentOnly: true });
2584
+ }
2585
+ addAttribute([[start, end]]);
2552
2586
  return;
2553
2587
  }
2554
2588
  else {
@@ -2620,7 +2654,14 @@ function handleAttribute(str, attr, parent, preserveCase, svelte5Plus, element)
2620
2654
  addAttribute(attributeName, attributeValue);
2621
2655
  }
2622
2656
  else if (attrVal.type == 'MustacheTag') {
2623
- attributeValue.push(rangeWithTrailingPropertyAccess(str.original, attrVal.expression));
2657
+ let [start, end] = rangeWithTrailingPropertyAccess(str.original, attrVal.expression);
2658
+ if (start === end) {
2659
+ // Loose parsing mode, we have an empty attribute value, e.g. attr={}
2660
+ // For proper intellisense we need to make this a non-empty expression.
2661
+ start--;
2662
+ str.overwrite(start, end, ' ', { contentOnly: true });
2663
+ }
2664
+ attributeValue.push([start, end]);
2624
2665
  addAttribute(attributeName, attributeValue);
2625
2666
  }
2626
2667
  return;
@@ -2706,7 +2747,7 @@ function handleAwait(str, awaitBlock) {
2706
2747
  transforms.push('}');
2707
2748
  }
2708
2749
  transforms.push('}');
2709
- transform(str, awaitBlock.start, awaitBlock.end, awaitBlock.end, transforms);
2750
+ transform(str, awaitBlock.start, awaitBlock.end, transforms);
2710
2751
  }
2711
2752
 
2712
2753
  /**
@@ -2899,17 +2940,18 @@ function handleDebug(str, debugBlock) {
2899
2940
  * `ensureArray` will error that there are more args than expected
2900
2941
  */
2901
2942
  function handleEach(str, eachBlock) {
2902
- var _a;
2903
- const startEnd = str.original.indexOf('}', ((_a = eachBlock.key) === null || _a === void 0 ? void 0 : _a.end) || eachBlock.context.end) + 1;
2943
+ var _a, _b, _c;
2944
+ const startEnd = str.original.indexOf('}', ((_a = eachBlock.key) === null || _a === void 0 ? void 0 : _a.end) || ((_b = eachBlock.context) === null || _b === void 0 ? void 0 : _b.end) || eachBlock.expression.end) + 1;
2904
2945
  let transforms;
2905
2946
  // {#each true, [1,2]} is valid but for (const x of true, [1,2]) is not if not wrapped with braces
2906
2947
  const containsComma = str.original
2907
2948
  .substring(eachBlock.expression.start, eachBlock.expression.end)
2908
2949
  .includes(',');
2909
2950
  const expressionEnd = getEnd(eachBlock.expression);
2910
- const contextEnd = getEnd(eachBlock.context);
2911
- const arrayAndItemVarTheSame = str.original.substring(eachBlock.expression.start, expressionEnd) ===
2912
- str.original.substring(eachBlock.context.start, contextEnd);
2951
+ const contextEnd = eachBlock.context && getEnd(eachBlock.context);
2952
+ const arrayAndItemVarTheSame = !!eachBlock.context &&
2953
+ str.original.substring(eachBlock.expression.start, expressionEnd) ===
2954
+ str.original.substring(eachBlock.context.start, contextEnd);
2913
2955
  if (arrayAndItemVarTheSame) {
2914
2956
  transforms = [
2915
2957
  `{ const $$_each = __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
@@ -2922,21 +2964,21 @@ function handleEach(str, eachBlock) {
2922
2964
  else {
2923
2965
  transforms = [
2924
2966
  'for(let ',
2925
- [eachBlock.context.start, contextEnd],
2967
+ eachBlock.context ? [eachBlock.context.start, contextEnd] : '$$each_item',
2926
2968
  ` of __sveltets_2_ensureArray(${containsComma ? '(' : ''}`,
2927
2969
  [eachBlock.expression.start, eachBlock.expression.end],
2928
- `${containsComma ? ')' : ''})){`
2970
+ `${containsComma ? ')' : ''})){${eachBlock.context ? '' : '$$each_item;'}`
2929
2971
  ];
2930
2972
  }
2931
2973
  if (eachBlock.index) {
2932
- const indexStart = str.original.indexOf(eachBlock.index, eachBlock.context.end);
2974
+ const indexStart = str.original.indexOf(eachBlock.index, ((_c = eachBlock.context) === null || _c === void 0 ? void 0 : _c.end) || eachBlock.expression.end);
2933
2975
  const indexEnd = indexStart + eachBlock.index.length;
2934
2976
  transforms.push('let ', [indexStart, indexEnd], ' = 1;');
2935
2977
  }
2936
2978
  if (eachBlock.key) {
2937
2979
  transforms.push([eachBlock.key.start, eachBlock.key.end], ';');
2938
2980
  }
2939
- transform(str, eachBlock.start, startEnd, startEnd, transforms);
2981
+ transform(str, eachBlock.start, startEnd, transforms);
2940
2982
  const endEach = str.original.lastIndexOf('{', eachBlock.end - 1);
2941
2983
  // {/each} -> } or {:else} -> }
2942
2984
  if (eachBlock.else) {
@@ -2945,12 +2987,20 @@ function handleEach(str, eachBlock) {
2945
2987
  str.overwrite(elseStart, elseEnd + 1, '}' + (arrayAndItemVarTheSame ? '}' : ''), {
2946
2988
  contentOnly: true
2947
2989
  });
2948
- str.remove(endEach, eachBlock.end);
2990
+ if (!isImplicitlyClosedBlock(endEach, eachBlock)) {
2991
+ str.remove(endEach, eachBlock.end);
2992
+ }
2949
2993
  }
2950
2994
  else {
2951
- str.overwrite(endEach, eachBlock.end, '}' + (arrayAndItemVarTheSame ? '}' : ''), {
2952
- contentOnly: true
2953
- });
2995
+ const closing = '}' + (arrayAndItemVarTheSame ? '}' : '');
2996
+ if (isImplicitlyClosedBlock(endEach, eachBlock)) {
2997
+ str.prependLeft(eachBlock.end, closing);
2998
+ }
2999
+ else {
3000
+ str.overwrite(endEach, eachBlock.end, closing, {
3001
+ contentOnly: true
3002
+ });
3003
+ }
2954
3004
  }
2955
3005
  }
2956
3006
 
@@ -2994,9 +3044,14 @@ function handleIf(str, ifBlock) {
2994
3044
  const expressionEnd = withTrailingPropertyAccess(str.original, ifBlock.expression.end);
2995
3045
  const end = str.original.indexOf('}', expressionEnd);
2996
3046
  str.overwrite(expressionEnd, end + 1, '){');
2997
- // {/if} -> }
2998
3047
  const endif = str.original.lastIndexOf('{', ifBlock.end - 1);
2999
- str.overwrite(endif, ifBlock.end, '}');
3048
+ if (isImplicitlyClosedBlock(endif, ifBlock)) {
3049
+ str.prependLeft(ifBlock.end, '}');
3050
+ }
3051
+ else {
3052
+ // {/if} -> }
3053
+ str.overwrite(endif, ifBlock.end, '}');
3054
+ }
3000
3055
  }
3001
3056
  /**
3002
3057
  * {:else} ---> } else {
@@ -3026,7 +3081,9 @@ function handleKey(str, keyBlock) {
3026
3081
  str.overwrite(expressionEnd, end + 1, '; ');
3027
3082
  // {/key} ->
3028
3083
  const endKey = str.original.lastIndexOf('{', keyBlock.end - 1);
3029
- str.overwrite(endKey, keyBlock.end, '', { contentOnly: true });
3084
+ if (!isImplicitlyClosedBlock(endKey, keyBlock)) {
3085
+ str.overwrite(endKey, keyBlock.end, '', { contentOnly: true });
3086
+ }
3030
3087
  }
3031
3088
 
3032
3089
  /**
@@ -3224,9 +3281,14 @@ function handleSnippet(str, snippetBlock, component) {
3224
3281
  const afterSnippet = isImplicitProp
3225
3282
  ? `};return __sveltets_2_any(0)}`
3226
3283
  : `};return __sveltets_2_any(0)};`;
3227
- str.overwrite(endSnippet, snippetBlock.end, afterSnippet, {
3228
- contentOnly: true
3229
- });
3284
+ if (isImplicitlyClosedBlock(endSnippet, snippetBlock)) {
3285
+ str.prependLeft(snippetBlock.end, afterSnippet);
3286
+ }
3287
+ else {
3288
+ str.overwrite(endSnippet, snippetBlock.end, afterSnippet, {
3289
+ contentOnly: true
3290
+ });
3291
+ }
3230
3292
  const lastParameter = (_a = snippetBlock.parameters) === null || _a === void 0 ? void 0 : _a.at(-1);
3231
3293
  const startEnd = str.original.indexOf('}', (_d = (_c = (_b = lastParameter === null || lastParameter === void 0 ? void 0 : lastParameter.typeAnnotation) === null || _b === void 0 ? void 0 : _b.end) !== null && _c !== void 0 ? _c : lastParameter === null || lastParameter === void 0 ? void 0 : lastParameter.end) !== null && _d !== void 0 ? _d : snippetBlock.expression.end) + 1;
3232
3294
  let parameters;
@@ -3239,7 +3301,22 @@ function handleSnippet(str, snippetBlock, component) {
3239
3301
  // inner async function for potential #await blocks
3240
3302
  const afterParameters = ` => { async ()${IGNORE_POSITION_COMMENT} => {`;
3241
3303
  if (isImplicitProp) {
3242
- str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { contentOnly: true });
3304
+ /** Can happen in loose parsing mode, e.g. code is currently `{#snippet }` */
3305
+ const emptyId = snippetBlock.expression.start === snippetBlock.expression.end;
3306
+ if (emptyId) {
3307
+ // Give intellisense a way to map into the right position for implicit prop completion
3308
+ str.overwrite(snippetBlock.start, snippetBlock.expression.start - 1, '', {
3309
+ contentOnly: true
3310
+ });
3311
+ str.overwrite(snippetBlock.expression.start - 1, snippetBlock.expression.start, ' ', {
3312
+ contentOnly: true
3313
+ });
3314
+ }
3315
+ else {
3316
+ str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', {
3317
+ contentOnly: true
3318
+ });
3319
+ }
3243
3320
  const transforms = ['('];
3244
3321
  if (parameters) {
3245
3322
  transforms.push(parameters);
@@ -3255,10 +3332,10 @@ function handleSnippet(str, snippetBlock, component) {
3255
3332
  transforms.push(')' + afterParameters);
3256
3333
  transforms.push([startEnd, snippetBlock.end]);
3257
3334
  if (component instanceof InlineComponent) {
3258
- component.addImplicitSnippetProp([snippetBlock.expression.start, snippetBlock.expression.end], transforms);
3335
+ component.addImplicitSnippetProp([snippetBlock.expression.start - (emptyId ? 1 : 0), snippetBlock.expression.end], transforms);
3259
3336
  }
3260
3337
  else {
3261
- component.addAttribute([[snippetBlock.expression.start, snippetBlock.expression.end]], transforms);
3338
+ component.addAttribute([[snippetBlock.expression.start - (emptyId ? 1 : 0), snippetBlock.expression.end]], transforms);
3262
3339
  }
3263
3340
  }
3264
3341
  else {
@@ -3273,7 +3350,7 @@ function handleSnippet(str, snippetBlock, component) {
3273
3350
  }
3274
3351
  transforms.push(')', surroundWithIgnoreComments(`: ReturnType<import('svelte').Snippet>`), // shows up nicely preserved on hover, other alternatives don't
3275
3352
  afterParameters);
3276
- transform(str, snippetBlock.start, startEnd, startEnd, transforms);
3353
+ transform(str, snippetBlock.start, startEnd, transforms);
3277
3354
  }
3278
3355
  }
3279
3356
  function handleImplicitChildren(componentNode, component) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte2tsx",
3
- "version": "0.7.29",
3
+ "version": "0.7.31",
4
4
  "description": "Convert Svelte components to TSX for type checking",
5
5
  "author": "David Pershouse",
6
6
  "license": "MIT",