Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
"estree-walker": "^2.0.1",
"lodash": "^4.17.19",
"prettier": "2.2.1",
"prettier-plugin-svelte": "~2.1.0",
"prettier-plugin-svelte": "~2.2.0",
"source-map": "^0.7.3",
"svelte": "~3.32.1",
"svelte": "~3.35.0",
"svelte-preprocess": "~4.6.1",
"svelte2tsx": "*",
"typescript": "*",
Expand Down
11 changes: 11 additions & 0 deletions packages/language-server/src/plugins/html/dataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,17 @@ const svelteTags: ITagData[] = [
}
]
},
{
name: 'svelte:fragment',
description:
'This element is useful if you want to assign a component to a named slot without creating a wrapper DOM element.',
attributes: [
{
name: 'slot',
description: 'The name of the named slot that should be targeted.'
}
]
},
{
name: 'slot',
description:
Expand Down
1 change: 0 additions & 1 deletion packages/svelte2tsx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ Input.svelte
</script>

<h1>hello {world}</h1>

```

will produce this ugly but type checkable TSX
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte2tsx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@rollup/plugin-typescript": "^6.0.0",
"source-map": "^0.6.1",
"source-map-support": "^0.5.16",
"svelte": "~3.32.1",
"svelte": "~3.35.0",
"tiny-glob": "^0.2.6",
"tslib": "^1.10.0",
"typescript": "^4.2.2"
Expand Down
10 changes: 8 additions & 2 deletions packages/svelte2tsx/src/htmlxtojsx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { handleBinding } from './nodes/binding';
import { handleClassDirective } from './nodes/class-directive';
import { handleComment } from './nodes/comment';
import { handleComponent } from './nodes/component';
import { handleSlot } from './nodes/slot';
import { handleDebug } from './nodes/debug';
import { handleEach } from './nodes/each';
import { handleElement } from './nodes/element';
Expand All @@ -20,6 +21,7 @@ import { handleRawHtml } from './nodes/raw-html';
import { handleSvelteTag } from './nodes/svelte-tag';
import { handleTransitionDirective } from './nodes/transition-directive';
import { handleText } from './nodes/text';
import { getSlotName } from '../utils/svelteAst';

type Walker = (node: Node, parent: Node, prop: string, index: number) => void;

Expand Down Expand Up @@ -72,10 +74,10 @@ export function convertHtmlxToJsx(
handleDebug(htmlx, str, node);
break;
case 'InlineComponent':
handleComponent(htmlx, str, node);
handleComponent(htmlx, str, node, parent);
break;
case 'Element':
handleElement(htmlx, str, node);
handleElement(htmlx, str, node, parent);
break;
case 'Comment':
handleComment(str, node);
Expand Down Expand Up @@ -113,6 +115,10 @@ export function convertHtmlxToJsx(
case 'Body':
handleSvelteTag(htmlx, str, node);
break;
case 'SlotTemplate':
handleSvelteTag(htmlx, str, node);
handleSlot(htmlx, str, node, parent, getSlotName(node) || 'default');
break;
case 'Text':
handleText(str, node);
break;
Expand Down
77 changes: 5 additions & 72 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';
import { getSlotName } from '../../utils/svelteAst';
import { beforeStart } from '../utils/node-utils';
import { getSingleSlotDef } from '../../svelte2tsx/nodes/slot';
import { handleSlot } from './slot';

/**
* Handle `<svelte:self>` and slot-specific transformations.
*/
export function handleComponent(htmlx: string, str: MagicString, el: Node): void {
export function handleComponent(htmlx: string, str: MagicString, el: Node, parent: Node): void {
//we need to remove : if it is a svelte component
if (el.name.startsWith('svelte:')) {
const colon = htmlx.indexOf(':', el.start);
Expand All @@ -20,73 +19,7 @@ export function handleComponent(htmlx: string, str: MagicString, el: Node): void
}
}

//we only need to do something if there is a let or slot
handleSlot(htmlx, str, el, el, 'default');

//walk the direct children looking for slots. We do this here because we need the name of our component for handleSlot
//we could lean on leave/enter, but I am lazy
if (!el.children) return;
for (const child of el.children) {
const slotName = getSlotName(child);
if (slotName) {
handleSlot(htmlx, str, child, el, slotName);
}
}
}

function handleSlot(
htmlx: string,
str: MagicString,
slotEl: Node,
component: Node,
slotName: string
): void {
//collect "let" definitions
const slotElIsComponent = slotEl === component;
let hasMoved = false;
let slotDefInsertionPoint: number;
for (const attr of slotEl.attributes) {
if (attr.type != 'Let') {
continue;
}

if (slotElIsComponent && slotEl.children.length == 0) {
//no children anyway, just wipe out the attribute
str.remove(attr.start, attr.end);
continue;
}

slotDefInsertionPoint =
slotDefInsertionPoint ||
(slotElIsComponent
? htmlx.lastIndexOf('>', slotEl.children[0].start) + 1
: slotEl.start);

str.move(attr.start, attr.end, slotDefInsertionPoint);

//remove let:
if (hasMoved) {
str.overwrite(attr.start, attr.start + 'let:'.length, ', ');
} else {
str.remove(attr.start, attr.start + 'let:'.length);
}
hasMoved = true;
if (attr.expression) {
//overwrite the = as a :
const equalSign = htmlx.lastIndexOf('=', attr.expression.start);
const curly = htmlx.lastIndexOf('{', beforeStart(attr.expression.start));
str.overwrite(equalSign, curly + 1, ':');
str.remove(attr.expression.end, attr.end);
}
}
if (!hasMoved) {
return;
}
str.appendLeft(slotDefInsertionPoint, '{() => { let {');
str.appendRight(slotDefInsertionPoint, `} = ${getSingleSlotDef(component, slotName)}` + ';<>');

const closeSlotDefInsertionPoint = slotElIsComponent
? htmlx.lastIndexOf('<', slotEl.end - 1)
: slotEl.end;
str.appendLeft(closeSlotDefInsertionPoint, '</>}}');
// Handle possible slot
const slotName = getSlotName(el) || 'default';
handleSlot(htmlx, str, el, slotName === 'default' ? el : parent, slotName);
}
9 changes: 8 additions & 1 deletion packages/svelte2tsx/src/htmlxtojsx/nodes/element.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';
import { getSlotName } from '../../utils/svelteAst';
import { handleSlot } from './slot';

/**
* Special treatment for self-closing / void tags to make them conform to JSX.
*/
export function handleElement(htmlx: string, str: MagicString, node: Node): void {
export function handleElement(htmlx: string, str: MagicString, node: Node, parent: Node): void {
const slotName = getSlotName(node);
if (slotName) {
handleSlot(htmlx, str, node, parent, slotName);
}

//we just have to self close void tags since jsx always wants the />
const voidTags = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'.split(
','
Expand Down
61 changes: 61 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/slot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';
import { beforeStart } from '../utils/node-utils';
import { getSingleSlotDef } from '../../svelte2tsx/nodes/slot';

export function handleSlot(
htmlx: string,
str: MagicString,
slotEl: Node,
component: Node,
slotName: string
): void {
//collect "let" definitions
const slotElIsComponent = slotEl === component;
let hasMoved = false;
let slotDefInsertionPoint: number;
for (const attr of slotEl.attributes) {
if (attr.type != 'Let') {
continue;
}

if (slotElIsComponent && slotEl.children.length == 0) {
//no children anyway, just wipe out the attribute
str.remove(attr.start, attr.end);
continue;
}

slotDefInsertionPoint =
slotDefInsertionPoint ||
(slotElIsComponent
? htmlx.lastIndexOf('>', slotEl.children[0].start) + 1
: slotEl.start);

str.move(attr.start, attr.end, slotDefInsertionPoint);

//remove let:
if (hasMoved) {
str.overwrite(attr.start, attr.start + 'let:'.length, ', ');
} else {
str.remove(attr.start, attr.start + 'let:'.length);
}
hasMoved = true;
if (attr.expression) {
//overwrite the = as a :
const equalSign = htmlx.lastIndexOf('=', attr.expression.start);
const curly = htmlx.lastIndexOf('{', beforeStart(attr.expression.start));
str.overwrite(equalSign, curly + 1, ':');
str.remove(attr.expression.end, attr.end);
}
}
if (!hasMoved) {
return;
}
str.appendLeft(slotDefInsertionPoint, '{() => { let {');
str.appendRight(slotDefInsertionPoint, `} = ${getSingleSlotDef(component, slotName)}` + ';<>');

const closeSlotDefInsertionPoint = slotElIsComponent
? htmlx.lastIndexOf('<', slotEl.end - 1)
: slotEl.end;
str.appendLeft(closeSlotDefInsertionPoint, '</>}}');
}
2 changes: 1 addition & 1 deletion packages/svelte2tsx/src/htmlxtojsx/nodes/svelte-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Node } from 'estree-walker';

/**
* `<svelte:window>...</svelte:window>` ----> `<sveltewindow>...</sveltewindow>`
* (same for :head, :body, :options)
* (same for :head, :body, :options, :fragment)
*/
export function handleSvelteTag(htmlx: string, str: MagicString, node: Node): void {
const colon = htmlx.indexOf(':', node.start);
Expand Down
6 changes: 4 additions & 2 deletions packages/svelte2tsx/svelte-jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ declare namespace svelte.JSX {

type NativeElement = HTMLElement;

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IntrinsicAttributes {}
interface IntrinsicAttributes {
slot?: string;
}

// TypeScript SVGElement has no `dataset` (Chrome 55+, Firefox 51+).
type Element = NativeElement & {
Expand Down Expand Up @@ -952,6 +953,7 @@ declare namespace svelte.JSX {
// Svelte specific
sveltewindow: HTMLProps<Window> & SvelteWindowProps;
sveltebody: HTMLProps<HTMLElement>;
sveltefragment: { slot?: string; };

[name: string]: { [name: string]: any };
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<><Parent >{() => { let {foo, bar:baz} = __sveltets_instanceOf(Parent).$$slot_def['default'];<>
{() => { let {bla} = __sveltets_instanceOf(Parent).$$slot_def['named'];<><Component >
{foo} {baz} {bla}
</Component></>}}
<Component >{() => { let {blubb} = __sveltets_instanceOf(Component).$$slot_def['default'];<>
{blubb}
</>}}</Component>
</>}}</Parent></>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Parent let:foo let:bar={baz}>
<Component slot="named" let:bla>
{foo} {baz} {bla}
</Component>
<Component let:blubb>
{blubb}
</Component>
</Parent>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<><Component>
<sveltefragment>
<p>hi</p>
</sveltefragment>

<sveltefragment >
<p>hi</p>
</sveltefragment>
</Component>

<Component>
{() => { let {foo, bar:baz} = __sveltets_instanceOf(Component).$$slot_def['default'];<><sveltefragment >
<p>{foo} {baz}</p>
</sveltefragment></>}}

{() => { let {foo, bar:baz} = __sveltets_instanceOf(Component).$$slot_def['named'];<><sveltefragment >
<p>{foo} {baz}</p>
</sveltefragment></>}}
</Component></>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Component>
<svelte:fragment>
<p>hi</p>
</svelte:fragment>

<svelte:fragment slot="named">
<p>hi</p>
</svelte:fragment>
</Component>

<Component>
<svelte:fragment let:foo let:bar={baz}>
<p>{foo} {baz}</p>
</svelte:fragment>

<svelte:fragment slot="named" let:foo let:bar={baz}>
<p>{foo} {baz}</p>
</svelte:fragment>
</Component>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<sveltehead>
<h1>Hi</h1>
</sveltehead>
<svelteoptions /></>
<svelteoptions />
<sveltefragment /></>
return { props: {}, slots: {}, getters: {}, events: {} }}

export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
<svelte:head>
<h1>Hi</h1>
</svelte:head>
<svelte:options />
<svelte:options />
<svelte:fragment />
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2108,10 +2108,10 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==

prettier-plugin-svelte@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.1.0.tgz#61b930107cf4eb8bdae0e9d416e47fb36ee6b53c"
integrity sha512-AeGJWicKCU9CbPKj9Wzk7apdCJwB8gzFHOMMqJh1X4LiwkMLHUjjysowH+SZfHdg69Hjv5rw5M7uJn0WobFhRQ==
prettier-plugin-svelte@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.2.0.tgz#4bd94992fa5b76413a8a5556f90b128c4fdaf7a6"
integrity sha512-Xdmqgr71tAuMqqzNCK52/v94g/Yv7V7lz+nmbO9NEA+9ol15VV3uUHOfydMNOo3SWvFaVlBcp947ebEaMWqVfQ==

prettier@2.2.1:
version "2.2.1"
Expand Down Expand Up @@ -2508,10 +2508,10 @@ svelte-preprocess@~4.6.1:
detect-indent "^6.0.0"
strip-indent "^3.0.0"

svelte@~3.32.1:
version "3.32.1"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.32.1.tgz#c4b6e35517d0ed77e652cc8964ef660afa2f70f3"
integrity sha512-j1KmD2ZOU0RGq1/STDXjwfh0/eJ/Deh2NXyuz1bpR9eOcz9yImn4CGxXdbSAN7cMTm9a7IyPUIbuBCzu/pXK0g==
svelte@~3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.35.0.tgz#e0d0ba60c4852181c2b4fd851194be6fda493e65"
integrity sha512-gknlZkR2sXheu/X+B7dDImwANVvK1R0QGQLd8CNIfxxGPeXBmePnxfzb6fWwTQRsYQG7lYkZXvpXJvxvpsoB7g==

table@^5.2.3:
version "5.4.6"
Expand Down