Skip to content

Commit 15b7590

Browse files
committed
fix hover support and refactor getsnippetdefinition
1 parent b2435ff commit 15b7590

File tree

5 files changed

+116
-32
lines changed

5 files changed

+116
-32
lines changed

.changeset/beige-boxes-lay.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@shopify/theme-language-server-common': minor
3+
---
4+
5+
Add hover support for @example in liquid doc tags
6+
EX:
7+
8+
```liquid
9+
{% doc %}
10+
@example
11+
{{ product }}
12+
{% enddoc %}
13+
```

packages/theme-language-server-common/src/hover/providers/RenderSnippetHoverProvider.spec.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,41 @@ describe('Module: RenderSnippetHoverProvider', async () => {
1616
name: 'title',
1717
description: 'The title of the product',
1818
type: 'string',
19+
nodeType: 'param',
1920
},
2021
{
2122
name: 'border-radius',
2223
description: 'The border radius in px',
2324
type: 'number',
25+
nodeType: 'param',
2426
},
2527
{
2628
name: 'no-type',
2729
description: 'This parameter has no type',
2830
type: null,
31+
nodeType: 'param',
2932
},
3033
{
3134
name: 'no-description',
3235
description: null,
3336
type: 'string',
37+
nodeType: 'param',
3438
},
3539
{
3640
name: 'no-type-or-description',
3741
description: null,
3842
type: null,
43+
nodeType: 'param',
3944
},
4045
],
4146
examples: [
4247
{
4348
content: '{{ product }}',
49+
nodeType: 'example',
50+
},
51+
{
52+
content: '{{ product.title }}',
53+
nodeType: 'example',
4454
},
4555
],
4656
},
@@ -68,10 +78,10 @@ describe('Module: RenderSnippetHoverProvider', async () => {
6878
});
6979

7080
describe('hover', () => {
71-
it('should return snippet definition with all parameters', async () => {
81+
it('should return snippet definition with all parameters and examples', async () => {
7282
await expect(provider).to.hover(
7383
`{% render 'product-car█d' %}`,
74-
'### product-card\n\n**Parameters:**\n- `title`: string - The title of the product\n- `border-radius`: number - The border radius in px\n- `no-type` - This parameter has no type\n- `no-description`: string\n- `no-type-or-description`\n\n**Examples:**\n\`\`\`liquid\n{{ product }}\n\`\`\`',
84+
'### product-card\n\n**Parameters:**\n- `title`: string - The title of the product\n- `border-radius`: number - The border radius in px\n- `no-type` - This parameter has no type\n- `no-description`: string\n- `no-type-or-description`\n\n**Examples:**\n```liquid\n{{ product }}\n```\n```liquid\n{{ product.title }}\n```',
7585
);
7686
});
7787

packages/theme-language-server-common/src/hover/providers/RenderSnippetHoverProvider.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class RenderSnippetHoverProvider implements BaseHoverProvider {
6262

6363
if (liquidDoc.examples?.length) {
6464
const examples = liquidDoc.examples
65-
?.map(({ content }) => `\`\`\`liquid\n${content}\n\`\`\``)
65+
?.map(({ content }) => `\`\`\`liquid\n${content.trimStart().trimEnd()}\n\`\`\``)
6666
.join('\n');
6767

6868
parts.push('', '**Examples:**', examples);

packages/theme-language-server-common/src/liquidDoc.spec.ts

+60-4
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,31 @@ describe('Unit: makeGetLiquidDocDefinitions', () => {
4747
name: 'firstParam',
4848
description: 'The first param',
4949
type: 'String',
50+
nodeType: 'param',
5051
},
5152
{
5253
name: 'secondParam',
5354
description: 'The second param',
5455
type: 'Number',
56+
nodeType: 'param',
5557
},
5658
{
5759
name: 'paramWithNoType',
5860
description: 'param with no type',
5961
type: null,
62+
nodeType: 'param',
6063
},
6164
{
6265
name: 'paramWithOnlyName',
6366
description: null,
6467
type: null,
68+
nodeType: 'param',
6569
},
6670
{
6771
name: 'paramWithNoDescription',
6872
description: null,
6973
type: 'Number',
74+
nodeType: 'param',
7075
},
7176
],
7277
examples: [],
@@ -87,7 +92,12 @@ describe('Unit: makeGetLiquidDocDefinitions', () => {
8792
name: 'product-card',
8893
liquidDoc: {
8994
parameters: [],
90-
examples: [{ content: '\n {{ product }}\n' }],
95+
examples: [
96+
{
97+
content: '\n {{ product }}\n',
98+
nodeType: 'example',
99+
},
100+
],
91101
},
92102
});
93103
});
@@ -106,7 +116,12 @@ describe('Unit: makeGetLiquidDocDefinitions', () => {
106116
name: 'product-card',
107117
liquidDoc: {
108118
parameters: [],
109-
examples: [{ content: '\n {{ product }}\n {{ product.title }}\n' }],
119+
examples: [
120+
{
121+
content: '\n {{ product }}\n {{ product.title }}\n',
122+
nodeType: 'example',
123+
},
124+
],
110125
},
111126
});
112127
});
@@ -124,8 +139,49 @@ describe('Unit: makeGetLiquidDocDefinitions', () => {
124139
expect(result).to.deep.equal({
125140
name: 'product-card',
126141
liquidDoc: {
127-
parameters: [{ name: 'product', description: 'The product', type: 'String' }],
128-
examples: [{ content: '\n {{ product }} // This is an example\n' }],
142+
parameters: [
143+
{
144+
name: 'product',
145+
description: 'The product',
146+
type: 'String',
147+
nodeType: 'param',
148+
},
149+
],
150+
examples: [
151+
{
152+
content: '\n {{ product }} // This is an example\n',
153+
nodeType: 'example',
154+
},
155+
],
156+
},
157+
});
158+
});
159+
160+
it('should extract multiple examples from @example annotations', async () => {
161+
const ast = toAST(`
162+
{% doc %}
163+
@example
164+
{{ product }}
165+
@example
166+
{{ product.title }}
167+
{% enddoc %}
168+
`);
169+
170+
const result = getSnippetDefinition(ast, 'product-card');
171+
expect(result).to.deep.equal({
172+
name: 'product-card',
173+
liquidDoc: {
174+
parameters: [],
175+
examples: [
176+
{
177+
content: '\n {{ product }}\n',
178+
nodeType: 'example',
179+
},
180+
{
181+
content: '\n {{ product.title }}\n',
182+
nodeType: 'example',
183+
},
184+
],
129185
},
130186
});
131187
});

packages/theme-language-server-common/src/liquidDoc.ts

+30-25
Original file line numberDiff line numberDiff line change
@@ -19,43 +19,48 @@ type LiquidDocDefinition = {
1919
examples?: LiquidDocExample[]; // I don't think we need an array but maybe we'll allow multiple examples
2020
};
2121

22-
export type LiquidDocParameter = {
22+
interface LiquidDocNode {
23+
nodeType: 'param' | 'example';
24+
}
25+
26+
export interface LiquidDocParameter extends LiquidDocNode {
2327
name: string;
2428
description: string | null;
2529
type: string | null;
26-
};
30+
nodeType: 'param';
31+
}
2732

28-
export type LiquidDocExample = {
33+
export interface LiquidDocExample extends LiquidDocNode {
2934
content: string;
30-
};
35+
nodeType: 'example';
36+
}
3137

3238
export function getSnippetDefinition(
3339
snippet: LiquidHtmlNode,
3440
snippetName: string,
3541
): SnippetDefinition {
36-
const parameters: LiquidDocParameter[] = visit<SourceCodeType.LiquidHtml, LiquidDocParameter>(
37-
snippet,
38-
{
39-
LiquidDocParamNode(node: LiquidDocParamNode) {
40-
return {
41-
name: node.paramName.value,
42-
description: node.paramDescription?.value ?? null,
43-
type: node.paramType?.value ?? null,
44-
};
45-
},
42+
const nodes: (LiquidDocParameter | LiquidDocExample)[] = visit<
43+
SourceCodeType.LiquidHtml,
44+
LiquidDocParameter | LiquidDocExample
45+
>(snippet, {
46+
LiquidDocParamNode(node: LiquidDocParamNode) {
47+
return {
48+
name: node.paramName.value,
49+
description: node.paramDescription?.value ?? null,
50+
type: node.paramType?.value ?? null,
51+
nodeType: 'param',
52+
};
4653
},
47-
);
48-
49-
const examples: LiquidDocExample[] = visit<SourceCodeType.LiquidHtml, LiquidDocExample>(
50-
snippet,
51-
{
52-
LiquidDocExampleNode(node: LiquidDocExampleNode) {
53-
return {
54-
content: node.exampleContent.value,
55-
};
56-
},
54+
LiquidDocExampleNode(node: LiquidDocExampleNode) {
55+
return {
56+
content: node.exampleContent.value,
57+
nodeType: 'example',
58+
};
5759
},
58-
);
60+
});
61+
62+
const parameters = nodes.filter((node): node is LiquidDocParameter => node.nodeType === 'param');
63+
const examples = nodes.filter((node): node is LiquidDocExample => node.nodeType === 'example');
5964

6065
return {
6166
name: snippetName,

0 commit comments

Comments
 (0)