From ac10174d3ccda134d0d43d3191e2ce8acfe91c30 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:35:04 +0100 Subject: [PATCH] feat: make use of loose Svelte parser and provide better intellisense (#2631) This adds support for the Svelte parser with its new loose mode (https://github.com/sveltejs/svelte/pull/14691) and adjusts code paths to make use of it properly. As a result, intellisense should be a lot more useful in situations where code is in the middle of being typed and the Svelte file is in a broken state. --- .../typescript/features/CompletionProvider.ts | 12 ++++++- .../src/htmlxtojsx_v2/nodes/Attribute.ts | 18 ++++++++-- .../nodes/AwaitPendingCatchBlock.ts | 2 +- .../src/htmlxtojsx_v2/nodes/EachBlock.ts | 25 +++++++++---- .../src/htmlxtojsx_v2/nodes/Element.ts | 6 ++-- .../src/htmlxtojsx_v2/nodes/IfElseBlock.ts | 10 ++++-- .../htmlxtojsx_v2/nodes/InlineComponent.ts | 23 +++++++----- .../svelte2tsx/src/htmlxtojsx_v2/nodes/Key.ts | 6 ++-- .../src/htmlxtojsx_v2/nodes/SnippetBlock.ts | 36 ++++++++++++++----- .../src/htmlxtojsx_v2/utils/node-utils.ts | 21 ++++++++++- packages/svelte2tsx/src/utils/htmlxparser.ts | 16 ++++++--- .../editing-binding/expected-svelte5.js | 6 ++-- .../expected.error.json | 20 +++++++++++ .../editing-empty-expression.v5/expectedv2.js | 7 ++++ .../editing-empty-expression.v5/input.svelte | 7 ++++ .../expected.error.json | 20 +++++++++++ .../editing-unclosed-block.v5/expectedv2.js | 12 +++++++ .../editing-unclosed-block.v5/input.svelte | 12 +++++++ .../expected.error.json | 20 +++++++++++ .../editing-unclosed-tag.v5/expectedv2.js | 6 ++++ .../editing-unclosed-tag.v5/input.svelte | 7 ++++ 21 files changed, 249 insertions(+), 43 deletions(-) create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expected.error.json create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expectedv2.js create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/input.svelte create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expected.error.json create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expectedv2.js create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/input.svelte create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expected.error.json create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expectedv2.js create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/input.svelte diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index d026efe42..2d6eee0eb 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -158,7 +158,7 @@ export class CompletionsProviderImpl implements CompletionsProvider mapped one character too short -> adjust + if ( + !inScript && + wordInfo.word === '' && + document.getText()[originalOffset - 1] === '.' && + tsDoc.getFullText()[offset] === '.' + ) { + offset++; + } + const componentInfo = getComponentAtPosition(lang, document, tsDoc, position); const attributeContext = componentInfo && getAttributeContextAtPosition(document, position); const eventAndSlotLetCompletions = this.getEventAndSlotLetCompletions( diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts index 0ef4095cc..d68e24078 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Attribute.ts @@ -128,7 +128,14 @@ export function handleAttribute( if (attributeValueIsOfType(attr.value, 'AttributeShorthand')) { // For the attribute shorthand, the name will be the mapped part - addAttribute([[attr.value[0].start, attr.value[0].end]]); + let [start, end] = [attr.value[0].start, attr.value[0].end]; + if (start === end) { + // Loose parsing mode, we have an empty attribute value, e.g. {} + // For proper intellisense we need to make this a non-empty expression. + start--; + str.overwrite(start, end, ' ', { contentOnly: true }); + } + addAttribute([[start, end]]); return; } else { let name = @@ -208,7 +215,14 @@ export function handleAttribute( addAttribute(attributeName, attributeValue); } else if (attrVal.type == 'MustacheTag') { - attributeValue.push(rangeWithTrailingPropertyAccess(str.original, attrVal.expression)); + let [start, end] = rangeWithTrailingPropertyAccess(str.original, attrVal.expression); + if (start === end) { + // Loose parsing mode, we have an empty attribute value, e.g. attr={} + // For proper intellisense we need to make this a non-empty expression. + start--; + str.overwrite(start, end, ' ', { contentOnly: true }); + } + attributeValue.push([start, end]); addAttribute(attributeName, attributeValue); } return; diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/AwaitPendingCatchBlock.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/AwaitPendingCatchBlock.ts index 89a539e65..d322dd9c5 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/AwaitPendingCatchBlock.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/AwaitPendingCatchBlock.ts @@ -78,5 +78,5 @@ export function handleAwait(str: MagicString, awaitBlock: BaseNode): void { transforms.push('}'); } transforms.push('}'); - transform(str, awaitBlock.start, awaitBlock.end, awaitBlock.end, transforms); + transform(str, awaitBlock.start, awaitBlock.end, transforms); } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts index 51dcb950e..d10c96bb4 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts @@ -1,6 +1,11 @@ import MagicString from 'magic-string'; import { BaseNode } from '../../interfaces'; -import { getEnd, transform, TransformationArray } from '../utils/node-utils'; +import { + getEnd, + isImplicitlyClosedBlock, + transform, + TransformationArray +} from '../utils/node-utils'; /** * Transform #each into a for-of loop @@ -65,7 +70,7 @@ export function handleEach(str: MagicString, eachBlock: BaseNode): void { if (eachBlock.key) { transforms.push([eachBlock.key.start, eachBlock.key.end], ';'); } - transform(str, eachBlock.start, startEnd, startEnd, transforms); + transform(str, eachBlock.start, startEnd, transforms); const endEach = str.original.lastIndexOf('{', eachBlock.end - 1); // {/each} -> } or {:else} -> } @@ -75,10 +80,18 @@ export function handleEach(str: MagicString, eachBlock: BaseNode): void { str.overwrite(elseStart, elseEnd + 1, '}' + (arrayAndItemVarTheSame ? '}' : ''), { contentOnly: true }); - str.remove(endEach, eachBlock.end); + + if (!isImplicitlyClosedBlock(endEach, eachBlock)) { + str.remove(endEach, eachBlock.end); + } } else { - str.overwrite(endEach, eachBlock.end, '}' + (arrayAndItemVarTheSame ? '}' : ''), { - contentOnly: true - }); + const closing = '}' + (arrayAndItemVarTheSame ? '}' : ''); + if (isImplicitlyClosedBlock(endEach, eachBlock)) { + str.prependLeft(eachBlock.end, closing); + } else { + str.overwrite(endEach, eachBlock.end, closing, { + contentOnly: true + }); + } } } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts index 5b0b30285..e7d1b4a55 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Element.ts @@ -205,7 +205,7 @@ export class Element { } if (this.isSelfclosing) { - transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [ + transform(this.str, this.startTagStart, this.startTagEnd, [ // Named slot transformations go first inside a outer block scope because //
means "use the x of let:x", and without a separate // block scope this would give a "used before defined" error @@ -217,7 +217,7 @@ export class Element { ...this.endTransformation ]); } else { - transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [ + transform(this.str, this.startTagStart, this.startTagEnd, [ ...slotLetTransformation, ...this.actionsTransformation, ...this.getStartTransformation(), @@ -230,7 +230,7 @@ export class Element { .lastIndexOf(`fooo

anothertag

` const endStart = tagEndIdx === -1 ? this.node.end : tagEndIdx + this.node.start; - transform(this.str, endStart, this.node.end, this.node.end, this.endTransformation); + transform(this.str, endStart, this.node.end, this.endTransformation); } } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/IfElseBlock.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/IfElseBlock.ts index b3380869f..658b13317 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/IfElseBlock.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/IfElseBlock.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { Node } from 'estree-walker'; -import { withTrailingPropertyAccess } from '../utils/node-utils'; +import { isImplicitlyClosedBlock, withTrailingPropertyAccess } from '../utils/node-utils'; /** * Transforms #if and :else if to a regular if control block. @@ -18,9 +18,13 @@ export function handleIf(str: MagicString, ifBlock: Node): void { const end = str.original.indexOf('}', expressionEnd); str.overwrite(expressionEnd, end + 1, '){'); - // {/if} -> } const endif = str.original.lastIndexOf('{', ifBlock.end - 1); - str.overwrite(endif, ifBlock.end, '}'); + if (isImplicitlyClosedBlock(endif, ifBlock)) { + str.prependLeft(ifBlock.end, '}'); + } else { + // {/if} -> } + str.overwrite(endif, ifBlock.end, '}'); + } } /** diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts index b1bab058d..66f6d0818 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts @@ -207,7 +207,7 @@ export class InlineComponent { if (this.isSelfclosing) { this.endTransformation.push('}'); - transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [ + transform(this.str, this.startTagStart, this.startTagEnd, [ // Named slot transformations go first inside a outer block scope because // means "use the x of let:x", and without a separate // block scope this would give a "used before defined" error @@ -221,17 +221,24 @@ export class InlineComponent { ...this.endTransformation ]); } else { - const endStart = - this.str.original - .substring(this.node.start, this.node.end) - .lastIndexOf(` -> Component} this.endTransformation.push([endStart + 2, endStart + this.node.name.length + 2]); } this.endTransformation.push('}'); - transform(this.str, this.startTagStart, this.startTagEnd, this.startTagEnd, [ + transform(this.str, this.startTagStart, this.startTagEnd, [ // See comment above why this goes first ...namedSlotLetTransformation, ...this.startTransformation, @@ -241,7 +248,7 @@ export class InlineComponent { snippetPropVariablesDeclaration, ...defaultSlotLetTransformation ]); - transform(this.str, endStart, this.node.end, this.node.end, this.endTransformation); + transform(this.str, endStart, this.node.end, this.endTransformation); } } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Key.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Key.ts index 6f0ae9ebe..edf269d2b 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Key.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Key.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { BaseNode } from '../../interfaces'; -import { withTrailingPropertyAccess } from '../utils/node-utils'; +import { isImplicitlyClosedBlock, withTrailingPropertyAccess } from '../utils/node-utils'; /** * {#key expr}content{/key} ---> expr; content @@ -14,5 +14,7 @@ export function handleKey(str: MagicString, keyBlock: BaseNode): void { // {/key} -> const endKey = str.original.lastIndexOf('{', keyBlock.end - 1); - str.overwrite(endKey, keyBlock.end, '', { contentOnly: true }); + if (!isImplicitlyClosedBlock(endKey, keyBlock)) { + str.overwrite(endKey, keyBlock.end, '', { contentOnly: true }); + } } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts index 701e1b83a..7dd6633a8 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { BaseNode } from '../../interfaces'; -import { transform, TransformationArray } from '../utils/node-utils'; +import { isImplicitlyClosedBlock, transform, TransformationArray } from '../utils/node-utils'; import { InlineComponent } from './InlineComponent'; import { IGNORE_POSITION_COMMENT, surroundWithIgnoreComments } from '../../utils/ignore'; import { Element } from './Element'; @@ -38,9 +38,13 @@ export function handleSnippet( ? `};return __sveltets_2_any(0)}` : `};return __sveltets_2_any(0)};`; - str.overwrite(endSnippet, snippetBlock.end, afterSnippet, { - contentOnly: true - }); + if (isImplicitlyClosedBlock(endSnippet, snippetBlock)) { + str.prependLeft(snippetBlock.end, afterSnippet); + } else { + str.overwrite(endSnippet, snippetBlock.end, afterSnippet, { + contentOnly: true + }); + } const lastParameter = snippetBlock.parameters?.at(-1); @@ -63,7 +67,23 @@ export function handleSnippet( const afterParameters = ` => { async ()${IGNORE_POSITION_COMMENT} => {`; if (isImplicitProp) { - str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { contentOnly: true }); + /** Can happen in loose parsing mode, e.g. code is currently `{#snippet }` */ + const emptyId = snippetBlock.expression.start === snippetBlock.expression.end; + + if (emptyId) { + // Give intellisense a way to map into the right position for implicit prop completion + str.overwrite(snippetBlock.start, snippetBlock.expression.start - 1, '', { + contentOnly: true + }); + str.overwrite(snippetBlock.expression.start - 1, snippetBlock.expression.start, ' ', { + contentOnly: true + }); + } else { + str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { + contentOnly: true + }); + } + const transforms: TransformationArray = ['(']; if (parameters) { @@ -82,12 +102,12 @@ export function handleSnippet( if (component instanceof InlineComponent) { component.addImplicitSnippetProp( - [snippetBlock.expression.start, snippetBlock.expression.end], + [snippetBlock.expression.start - (emptyId ? 1 : 0), snippetBlock.expression.end], transforms ); } else { component.addAttribute( - [[snippetBlock.expression.start, snippetBlock.expression.end]], + [[snippetBlock.expression.start - (emptyId ? 1 : 0), snippetBlock.expression.end]], transforms ); } @@ -109,7 +129,7 @@ export function handleSnippet( afterParameters ); - transform(str, snippetBlock.start, startEnd, startEnd, transforms); + transform(str, snippetBlock.start, startEnd, transforms); } } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts index d3c95164a..0630c71b3 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts @@ -27,7 +27,6 @@ export function transform( str: MagicString, start: number, end: number, - _xxx: number, // TODO transformations: TransformationArray ) { const moves: Array<[number, number]> = []; @@ -128,6 +127,10 @@ export function transform( } for (let i = deletePos; i < moves.length; i++) { + // Can happen when there's not enough space left at the end of an unfininished element/component tag. + // Better to leave potentially slightly disarranged code than fail loudly + if (moves[i][1] >= end && moves[i][0] <= end) break; + str.move(moves[i][0], moves[i][1], end); } } @@ -243,3 +246,19 @@ export function isTypescriptNode(node: any) { node.type === 'TSNonNullExpression' ); } + +/** + * Returns `true` if the given block is implicitly closed, which could be the case in loose parsing mode. + * E.g.: + * ```html + *
+ * {#if x} + *
+ * ``` + * @param end + * @param block + * @returns + */ +export function isImplicitlyClosedBlock(end: number, block: Node) { + return end < (block.children[block.children.length - 1]?.end ?? block.expression.end); +} diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index 3757f8452..999621d70 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -114,7 +114,7 @@ function blankVerbatimContent(htmlx: string, verbatimElements: Node[]) { export function parseHtmlx( htmlx: string, parse: typeof import('svelte/compiler').parse, - options: { emitOnTemplateError?: boolean } + options: { emitOnTemplateError?: boolean; svelte5Plus: boolean } ) { //Svelte tries to parse style and script tags which doesn't play well with typescript, so we blank them out. //HTMLx spec says they should just be retained after processing as is, so this is fine @@ -122,10 +122,16 @@ export function parseHtmlx( const deconstructed = blankVerbatimContent(htmlx, verbatimElements); //extract the html content parsed as htmlx this excludes our script and style tags - const parsingCode = options.emitOnTemplateError - ? blankPossiblyErrorOperatorOrPropertyAccess(deconstructed) - : deconstructed; - const htmlxAst = parse(parsingCode).html as any; + const parsingCode = + options.emitOnTemplateError && !options.svelte5Plus + ? blankPossiblyErrorOperatorOrPropertyAccess(deconstructed) + : deconstructed; + const htmlxAst = ( + parse( + parsingCode, + options.svelte5Plus ? ({ loose: options.emitOnTemplateError } as any) : undefined + ) as any + ).html; //restore our script and style tags as nodes to maintain validity with HTMLx for (const s of verbatimElements) { diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js index 7bb207fea..4e136e37c 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expected-svelte5.js @@ -1,3 +1,3 @@ - { svelteHTML.createElement("input", { });obj = __sveltets_2_any(null);} - { svelteHTML.createElement("input", { "bind:value":obj.,});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/} - { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} \ No newline at end of file + { svelteHTML.createElement("input", { });obj. = __sveltets_2_any(null);} + { svelteHTML.createElement("input", { "bind:value":obj.,});/*Ωignore_startΩ*/() => obj. = __sveltets_2_any(null);/*Ωignore_endΩ*/} + { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); const $$_tupnI0 = new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj. = __sveltets_2_any(null);/*Ωignore_endΩ*/$$_tupnI0.$$bindings = 'value';} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expected.error.json b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expected.error.json new file mode 100644 index 000000000..f351fbbd7 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expected.error.json @@ -0,0 +1,20 @@ +{ + "code": "js_parse_error", + "message": "Unexpected token\nhttps://svelte.dev/e/js_parse_error", + "filename": "(unknown)", + "start": { + "line": 1, + "column": 6, + "character": 6 + }, + "end": { + "line": 1, + "column": 6, + "character": 6 + }, + "position": [ + 6, + 6 + ], + "frame": "1:
{}
\n ^\n2: \n3:
" +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expectedv2.js new file mode 100644 index 000000000..cf23d1cda --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/expectedv2.js @@ -0,0 +1,7 @@ + { svelteHTML.createElement("div", {});; } + + { svelteHTML.createElement("div", {"attr": ,}); } + { svelteHTML.createElement("div", { ,}); } + + { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: {"prop": ,}}); Component} + { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { ,}}); Component} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/input.svelte new file mode 100644 index 000000000..aabd2d50e --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-empty-expression.v5/input.svelte @@ -0,0 +1,7 @@ +
{}
+ +
+
+ + + diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expected.error.json b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expected.error.json new file mode 100644 index 000000000..73fd4ce8d --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expected.error.json @@ -0,0 +1,20 @@ +{ + "code": "element_invalid_closing_tag", + "message": "`
` attempted to close an element that was not open\nhttps://svelte.dev/e/element_invalid_closing_tag", + "filename": "(unknown)", + "start": { + "line": 3, + "column": 0, + "character": 20 + }, + "end": { + "line": 3, + "column": 0, + "character": 20 + }, + "position": [ + 20, + 20 + ], + "frame": "1:
\n2: {#if foo}\n3:
\n ^\n4: \n5:
" +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expectedv2.js new file mode 100644 index 000000000..b93c8d8cf --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/expectedv2.js @@ -0,0 +1,12 @@ + { svelteHTML.createElement("div", {}); + if(foo){ +} } + + { svelteHTML.createElement("div", {}); + for(let item of __sveltets_2_ensureArray(array)){ + if(i){ +}} } + + { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); const $$_tnenopmoC0 = new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: {f:() => { async ()/*Ωignore_positionΩ*/ => { +};return __sveltets_2_any(0)},}});/*Ωignore_startΩ*/const {f} = $$_tnenopmoC0.$$prop_def;/*Ωignore_endΩ*/ + Component} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/input.svelte new file mode 100644 index 000000000..4b4130816 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-block.v5/input.svelte @@ -0,0 +1,12 @@ +
+ {#if foo} +
+ +
+ {#each array as item} + {#if i} +
+ + + {#snippet f} + diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expected.error.json b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expected.error.json new file mode 100644 index 000000000..d7dea39f6 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expected.error.json @@ -0,0 +1,20 @@ +{ + "code": "expected_token", + "message": "Expected token >\nhttps://svelte.dev/e/expected_token", + "filename": "(unknown)", + "start": { + "line": 3, + "column": 2, + "character": 24 + }, + "end": { + "line": 3, + "column": 2, + "character": 24 + }, + "position": [ + 24, + 24 + ], + "frame": "1:
\n2: \n ^\n4: \n5:
" +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expectedv2.js new file mode 100644 index 000000000..5b87c6c58 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/expectedv2.js @@ -0,0 +1,6 @@ + { svelteHTML.createElement("div", {}); + { const $$_pmoC1C = __sveltets_2_ensureComponent(Comp); new $$_pmoC1C({ target: __sveltets_2_any(), props: { "a":b,}}); +} } + + { svelteHTML.createElement("div", {}); + { svelteHTML.createElement("span", { "a":b,});} } \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/input.svelte new file mode 100644 index 000000000..154202db2 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-tag.v5/input.svelte @@ -0,0 +1,7 @@ +
+ + +
+