Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/calm-plums-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme-language-server-common': patch
---

Add go-to-definition support for Liquid file references by reusing document-link targets (snippets, sections, content_for block types, and asset_url files).
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { findCurrentNode, SourceCodeType } from '@shopify/theme-check-common';
import { DefinitionLink, DefinitionParams } from 'vscode-languageserver';

import { AugmentedJsonSourceCode, DocumentManager } from '../documents';
import { FindThemeRootURI } from '../internal-types';
import { BaseDefinitionProvider } from './BaseDefinitionProvider';
import { DocumentLinksDefinitionProvider } from './providers/DocumentLinksDefinitionProvider';
import { SchemaTranslationStringDefinitionProvider } from './providers/SchemaTranslationStringDefinitionProvider';
import { TranslationStringDefinitionProvider } from './providers/TranslationStringDefinitionProvider';

Expand All @@ -13,8 +15,10 @@ export class DefinitionProvider {
private documentManager: DocumentManager,
getDefaultLocaleSourceCode: (uri: string) => Promise<AugmentedJsonSourceCode | null>,
getDefaultSchemaLocaleSourceCode: (uri: string) => Promise<AugmentedJsonSourceCode | null>,
findThemeRootURI: FindThemeRootURI,
) {
this.providers = [
new DocumentLinksDefinitionProvider(documentManager, findThemeRootURI),
new TranslationStringDefinitionProvider(documentManager, getDefaultLocaleSourceCode),
new SchemaTranslationStringDefinitionProvider(
documentManager,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { assert, beforeEach, describe, expect, it } from 'vitest';
import { DefinitionParams, LocationLink } from 'vscode-languageserver-protocol';

import { DocumentManager } from '../../documents';
import { DefinitionProvider } from '../DefinitionProvider';

describe('Module: DocumentLinksDefinitionProvider', () => {
let provider: DefinitionProvider;
let documentManager: DocumentManager;

beforeEach(() => {
documentManager = new DocumentManager();

provider = new DefinitionProvider(
documentManager,
async () => null,
async () => null,
async () => 'file:///theme',
);
});

it('finds definitions for Liquid file references', async () => {
const lines = [
"{% render 'stylesheets' %}",
"{% section 'header' %}",
"{{ 'theme.css' | asset_url }}",
"{% content_for 'block', type: 'feature-grid' %}",
];
const liquidContent = lines.join('\n');

documentManager.open('file:///theme/layout/theme.liquid', liquidContent, 1);

const cases = [
{
line: 0,
token: 'stylesheets',
targetUri: 'file:///theme/snippets/stylesheets.liquid',
},
{
line: 1,
token: 'header',
targetUri: 'file:///theme/sections/header.liquid',
},
{
line: 2,
token: 'theme.css',
targetUri: 'file:///theme/assets/theme.css',
},
{
line: 3,
token: 'feature-grid',
targetUri: 'file:///theme/blocks/feature-grid.liquid',
},
];

for (const testCase of cases) {
const params: DefinitionParams = {
textDocument: { uri: 'file:///theme/layout/theme.liquid' },
position: {
line: testCase.line,
character: lines[testCase.line].indexOf(testCase.token) + 1,
},
};

const result = await provider.definitions(params);

assert(result);
expect(result).toHaveLength(1);
assert(LocationLink.is(result[0]));
expect(result[0].targetUri).toBe(testCase.targetUri);
}
});

it('returns null when cursor is not on a file reference', async () => {
const source = "{% render 'stylesheets' %}";
documentManager.open('file:///theme/layout/theme.liquid', source, 1);

const params: DefinitionParams = {
textDocument: { uri: 'file:///theme/layout/theme.liquid' },
position: { line: 0, character: 4 }, // on `render`
};

const result = await provider.definitions(params);

expect(result).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { LiquidHtmlNode } from '@shopify/liquid-html-parser';
import {
DefinitionLink,
DefinitionParams,
LocationLink,
Range,
} from 'vscode-languageserver-protocol';

import { DocumentLinksProvider } from '../../documentLinks';
import { DocumentManager } from '../../documents';
import { FindThemeRootURI } from '../../internal-types';
import { BaseDefinitionProvider } from '../BaseDefinitionProvider';

export class DocumentLinksDefinitionProvider implements BaseDefinitionProvider {
private documentLinksProvider: DocumentLinksProvider;

constructor(
private documentManager: DocumentManager,
findThemeRootURI: FindThemeRootURI,
) {
this.documentLinksProvider = new DocumentLinksProvider(documentManager, findThemeRootURI);
}

async definitions(
params: DefinitionParams,
_node: LiquidHtmlNode,
_ancestors: LiquidHtmlNode[],
): Promise<DefinitionLink[]> {
const sourceCode = this.documentManager.get(params.textDocument.uri);
if (!sourceCode) {
return [];
}

const cursorOffset = sourceCode.textDocument.offsetAt(params.position);
const links = await this.documentLinksProvider.documentLinks(params.textDocument.uri);

const activeLink = links.find(({ range, target }) => {
if (!target) return false;

const start = sourceCode.textDocument.offsetAt(range.start);
const end = sourceCode.textDocument.offsetAt(range.end);
return cursorOffset >= start && cursorOffset <= end;
});

if (!activeLink?.target) {
return [];
}

// Unknown target document range is represented by the start of file.
const targetRange = Range.create(0, 0, 0, 0);

return [
LocationLink.create(activeLink.target, targetRange, targetRange, activeLink.range),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('Module: SchemaTranslationStringDefinitionProvider', () => {
documentManager,
async () => null,
mockGetDefaultSchemaLocaleSourceCode,
async () => null,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('Module: TranslationStringDefinitionProvider', () => {
documentManager,
mockGetDefaultLocaleSourceCode,
async () => null,
async () => null,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ export function startServer(
documentManager,
getDefaultLocaleSourceCode,
getDefaultSchemaLocaleSourceCode,
findThemeRootURI,
);
const jsonLanguageService = new JSONLanguageService(
documentManager,
Expand Down
Loading