Skip to content

Commit af6e743

Browse files
authored
(feat) better key transformations + hover + completion (#575)
#570
1 parent 2863414 commit af6e743

File tree

7 files changed

+47
-27
lines changed

7 files changed

+47
-27
lines changed

packages/language-server/src/plugins/svelte/features/SvelteTags.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { SvelteDocument } from '../SvelteDocument';
33
/**
44
* Special svelte syntax tags that do template logic.
55
*/
6-
export type SvelteLogicTag = 'each' | 'if' | 'await';
6+
export type SvelteLogicTag = 'each' | 'if' | 'await' | 'key';
77

88
/**
99
* Special svelte syntax tags.
@@ -43,6 +43,15 @@ Content that is conditionally rendered can be wrapped in an if block.
4343
\`{#if expression}...{:else}...{/if}\`\\
4444
\\
4545
https://svelte.dev/docs#if
46+
`,
47+
key: `\`{#key expression}...{/key}\`\\
48+
Key blocks destroy and recreate their contents when the value of an expression changes.\\
49+
This is useful if you want an element to play its transition whenever a value changes.\\
50+
When used around components, this will cause them to be reinstantiated and reinitialised.
51+
#### Usage:
52+
\`{#key expression}...{/key}\`\\
53+
\\
54+
https://svelte.dev/docs#key
4655
`,
4756
html:
4857
`\`{@html ...}\`\\
@@ -88,6 +97,7 @@ export function getLatestOpeningTag(
8897
idxOfLastOpeningTag(content, 'each'),
8998
idxOfLastOpeningTag(content, 'if'),
9099
idxOfLastOpeningTag(content, 'await'),
100+
idxOfLastOpeningTag(content, 'key'),
91101
];
92102
const lastIdx = lastIdxs.sort((i1, i2) => i2.lastIdx - i1.lastIdx);
93103
return lastIdx[0].lastIdx === -1 ? null : lastIdx[0].tag;

packages/language-server/src/plugins/svelte/features/getCompletions.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const HTML_COMMENT_START = '<!--';
1515
const componentDocumentationCompletion: CompletionItem = {
1616
label: '@component',
1717
insertText: `component${EOL}$1${EOL}`,
18-
documentation: 'Documentation for this component. ' +
18+
documentation:
19+
'Documentation for this component. ' +
1920
'It will show up on hover. You can use markdown and code blocks here',
2021
insertTextFormat: InsertTextFormat.Snippet,
2122
kind: CompletionItemKind.Snippet,
@@ -66,9 +67,9 @@ export function getCompletions(
6667
}
6768

6869
const commentStartIndex = lastCharactersBeforePosition.lastIndexOf(HTML_COMMENT_START);
69-
const text = lastCharactersBeforePosition.substring(
70-
commentStartIndex + HTML_COMMENT_START.length
71-
).trimLeft();
70+
const text = lastCharactersBeforePosition
71+
.substring(commentStartIndex + HTML_COMMENT_START.length)
72+
.trimLeft();
7273

7374
if (componentDocumentationCompletion.label.includes(text)) {
7475
return CompletionList.create([componentDocumentationCompletion], false);
@@ -106,6 +107,7 @@ function getCompletionsWithRegardToTriggerCharacter(
106107
label: 'await then',
107108
insertText: 'await $1 then $2}\n\t$3\n{/await',
108109
},
110+
{ tag: 'key', label: 'key', insertText: 'key $1}\n\t$2\n{/key' },
109111
]);
110112
}
111113

@@ -133,6 +135,7 @@ function getCompletionsWithRegardToTriggerCharacter(
133135
awaitOpen: createCompletionItems([{ tag: 'await', label: 'await' }]),
134136
eachOpen: createCompletionItems([{ tag: 'each', label: 'each' }]),
135137
ifOpen: createCompletionItems([{ tag: 'if', label: 'if' }]),
138+
keyOpen: createCompletionItems([{ tag: 'key', label: 'key' }]),
136139
},
137140
svelteDoc,
138141
offset,
@@ -163,7 +166,12 @@ function getTriggerCharacter(content: string) {
163166
* Return completions with regards to last opened tag.
164167
*/
165168
function showCompletionWithRegardsToOpenedTags(
166-
on: { eachOpen: CompletionList; ifOpen: CompletionList; awaitOpen: CompletionList },
169+
on: {
170+
eachOpen: CompletionList;
171+
ifOpen: CompletionList;
172+
awaitOpen: CompletionList;
173+
keyOpen?: CompletionList;
174+
},
167175
svelteDoc: SvelteDocument,
168176
offset: number,
169177
) {
@@ -174,6 +182,8 @@ function showCompletionWithRegardsToOpenedTags(
174182
return on.ifOpen;
175183
case 'await':
176184
return on.awaitOpen;
185+
case 'key':
186+
return on?.keyOpen ?? null;
177187
default:
178188
return null;
179189
}

packages/language-server/src/plugins/svelte/features/getHoverInfo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ const tagPossibilities: { tag: SvelteTag | ':else'; values: string[] }[] = [
7979
{ tag: 'each' as const, values: ['#each', '/each'] },
8080
// await
8181
{ tag: 'await' as const, values: ['#await', '/await', ':then', ':catch'] },
82+
// key
83+
{ tag: 'key' as const, values: ['#key', '/key'] },
8284
// @
8385
{ tag: 'html' as const, values: ['@html'] },
8486
{ tag: 'debug' as const, values: ['@debug'] },

packages/language-server/test/plugins/svelte/features/getCompletions.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe('SveltePlugin#getCompletions', () => {
5050
});
5151

5252
it('should return completions for #', () => {
53-
expectCompletionsFor('{#').toEqual(['if', 'each', 'await :then', 'await then']);
53+
expectCompletionsFor('{#').toEqual(['if', 'each', 'await :then', 'await then', 'key']);
5454
});
5555

5656
it('should return completions for @', () => {
@@ -112,6 +112,10 @@ describe('SveltePlugin#getCompletions', () => {
112112
expectCompletionsFor('{#await}{/').toEqual(['await']);
113113
});
114114

115+
it('for key', () => {
116+
expectCompletionsFor('{#key}{/').toEqual(['key']);
117+
});
118+
115119
it('for last open tag', () => {
116120
expectCompletionsFor('{#if}{/if}{#if}{#await}{/').toEqual(['await']);
117121
});
@@ -120,13 +124,7 @@ describe('SveltePlugin#getCompletions', () => {
120124
it('should return completion for component documentation comment', () => {
121125
const content = '<!--@';
122126
const svelteDoc = new SvelteDocument(new Document('url', content));
123-
const completions = getCompletions(
124-
svelteDoc,
125-
Position.create(0, content.length)
126-
);
127-
assert.deepStrictEqual(
128-
completions?.items?.[0].insertText,
129-
`component${EOL}$1${EOL}`
130-
);
127+
const completions = getCompletions(svelteDoc, Position.create(0, content.length));
128+
assert.deepStrictEqual(completions?.items?.[0].insertText, `component${EOL}$1${EOL}`);
131129
});
132130
});

packages/language-server/test/plugins/svelte/features/getHoverInfo.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe('SveltePlugin#getHoverInfo', () => {
6666
});
6767

6868
describe('should return hover for #', () => {
69-
(['if', 'each', 'await'] as const).forEach((tag) => {
69+
(['if', 'each', 'await', 'key'] as const).forEach((tag) => {
7070
it(`(#${tag})`, () => {
7171
expectHoverInfoFor(`{#${tag}}`, Position.create(0, 3)).toEqual(tag);
7272
expectHoverInfoFor(`{#${tag} `, Position.create(0, 3)).toEqual(tag);

packages/svelte2tsx/src/htmlxtojsx/nodes/key.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import MagicString from 'magic-string';
22
import { Node } from 'estree-walker';
33

44
/**
5-
* {#key expr}content{/key} ---> {expr || <>...</>}
5+
* {#key expr}content{/key} ---> {expr} content
66
*/
77
export function handleKey(htmlx: string, str: MagicString, keyBlock: Node): void {
8-
// {#key expr} -> {expr || <>
8+
// {#key expr} -> {expr}
99
str.overwrite(keyBlock.start, keyBlock.expression.start, '{');
1010
const end = htmlx.indexOf('}', keyBlock.expression.end);
11-
str.overwrite(keyBlock.expression.end, end + 1, ' || <>');
11+
str.overwrite(keyBlock.expression.end, end + 1, '} ');
1212

13-
// {/key} -> </>}
13+
// {/key} ->
1414
const endKey = htmlx.lastIndexOf('{', keyBlock.end - 1);
15-
str.overwrite(endKey, keyBlock.end, '</>}');
15+
str.remove(endKey, keyBlock.end);
1616
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
<>{value || <>
1+
<>{value}
22
<p>hello</p>
3-
</>}
4-
{$store || <>
3+
4+
{$store}
55
<p>hello</p>
6-
</>}
7-
{expr.obj || <>
6+
7+
{expr.obj}
88
<p>hello</p>
9-
</>}</>
9+
</>

0 commit comments

Comments
 (0)