Skip to content

Commit

Permalink
add deeplinking for block types
Browse files Browse the repository at this point in the history
  • Loading branch information
itsjustriley committed Dec 16, 2024
1 parent e61fa07 commit 6d5ccd2
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,20 @@ describe('DocumentLinksProvider', () => {
expect(result[i].target).toBe(expectedUrls[i]);
}
});

it('should return a list of document links with correct URLs for a LiquidRawTag document', async () => {
uriString = 'file:///path/to/liquid-raw-tag-document.liquid';
rootUri = 'file:///path/to/project';

const liquidRawTagContent = `
{% schema %}
{ "blocks": [{ "type": "valid" }] }
{% endschema %}
`;

documentManager.open(uriString, liquidRawTagContent, 1);

const result = await documentLinksProvider.documentLinks(uriString);
expect(result).toEqual([]);

Check failure on line 78 in packages/theme-language-server-common/src/documentLinks/DocumentLinksProvider.spec.ts

View workflow job for this annotation

GitHub Actions / Tests / OS ubuntu-latest / NodeJS 18

packages/theme-language-server-common/src/documentLinks/DocumentLinksProvider.spec.ts > DocumentLinksProvider > should return a list of document links with correct URLs for a LiquidRawTag document

AssertionError: expected [ { range: { …(2) }, …(2) } ] to deeply equal [] - Expected + Received - Array [] + Array [ + Object { + "data": undefined, + "range": Object { + "end": Object { + "character": 38, + "line": 2, + }, + "start": Object { + "character": 31, + "line": 2, + }, + }, + "target": "file:///path/to/project/blocks/valid.liquid", + }, + ] ❯ packages/theme-language-server-common/src/documentLinks/DocumentLinksProvider.spec.ts:78:20

Check failure on line 78 in packages/theme-language-server-common/src/documentLinks/DocumentLinksProvider.spec.ts

View workflow job for this annotation

GitHub Actions / Tests / OS ubuntu-latest / NodeJS 20

packages/theme-language-server-common/src/documentLinks/DocumentLinksProvider.spec.ts > DocumentLinksProvider > should return a list of document links with correct URLs for a LiquidRawTag document

AssertionError: expected [ { range: { …(2) }, …(2) } ] to deeply equal [] - Expected + Received - Array [] + Array [ + Object { + "data": undefined, + "range": Object { + "end": Object { + "character": 38, + "line": 2, + }, + "start": Object { + "character": 31, + "line": 2, + }, + }, + "target": "file:///path/to/project/blocks/valid.liquid", + }, + ] ❯ packages/theme-language-server-common/src/documentLinks/DocumentLinksProvider.spec.ts:78:20
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LiquidHtmlNode, LiquidString, NodeTypes } from '@shopify/liquid-html-parser';
import { LiquidHtmlNode, LiquidRawTag, LiquidString, NodeTypes } from '@shopify/liquid-html-parser';
import { SourceCodeType } from '@shopify/theme-check-common';
import { DocumentLink, Range } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
Expand All @@ -7,6 +7,8 @@ import { URI, Utils } from 'vscode-uri';
import { DocumentManager } from '../documents';
import { visit, Visitor } from '@shopify/theme-check-common';

import { parseTree, findNodeAtLocation, ParseError, Node as JSONNode } from 'jsonc-parser';

export class DocumentLinksProvider {
constructor(
private documentManager: DocumentManager,
Expand Down Expand Up @@ -79,6 +81,42 @@ function documentLinksVisitor(
Utils.resolvePath(root, 'assets', expression.value).toString(),
);
},

LiquidRawTag(node) {
// look for schema tags
if (node.name === 'schema') {
// parse and return a tree of the schema
const errors: ParseError[] = [];
const jsonNode = parseTree(node.body.value, errors);
if (!jsonNode || errors.length > 0) {
return [];
}

// create an array of links so we can process all block types and preset block types in the schema
const links: DocumentLink[] = [];

// Process top-level blocks
const blocksNode = findNodeAtLocation(jsonNode, ['blocks']);
if (blocksNode && blocksNode.type === 'array' && blocksNode.children) {
links.push(...createLinksFromBlocks(blocksNode, node, textDocument, root));
}

// Process presets
const presetsNode = findNodeAtLocation(jsonNode, ['presets']);
if (presetsNode && presetsNode.type === 'array' && presetsNode.children) {
presetsNode.children.forEach((presetNode) => {
// Process blocks within each preset
const presetBlocksNode = findNodeAtLocation(presetNode, ['blocks']);
if (presetBlocksNode) {
links.push(...processPresetBlocks(presetBlocksNode, node, textDocument, root));
}
});
}

return links;
}
return [];
},
};
}

Expand All @@ -91,3 +129,149 @@ function range(textDocument: TextDocument, node: { position: LiquidHtmlNode['pos
function isLiquidString(node: LiquidHtmlNode): node is LiquidString {
return node.type === NodeTypes.String;
}

function createDocumentLinkForTypeNode(
typeNode: JSONNode,
parentNode: LiquidRawTag,
textDocument: TextDocument,
root: URI,
blockType: string,
): DocumentLink | null {
const startOffset = typeNode.offset;
const endOffset = typeNode.offset + typeNode.length;
const startPos = parentNode.body.position.start + startOffset;
const endPos = parentNode.body.position.start + endOffset;

const start = textDocument.positionAt(startPos);
const end = textDocument.positionAt(endPos);

return DocumentLink.create(
Range.create(start, end),
Utils.resolvePath(root, 'blocks', `${blockType}.liquid`).toString(),
);
}

function processPresetBlocks(
blocksNode: JSONNode,
parentNode: LiquidRawTag,
textDocument: TextDocument,
root: URI,
): DocumentLink[] {
const links: DocumentLink[] = [];

if (blocksNode.type === 'object' && blocksNode.children) {
blocksNode.children.forEach((propertyNode) => {
const blockValueNode = propertyNode.children?.[1]; // The value node of the property
if (!blockValueNode) return;

// Check if the block has a 'name' key so we don't deeplink inline block types
const nameNode = findNodeAtLocation(blockValueNode, ['name']);
if (nameNode) {
return;
}

const typeNode = findNodeAtLocation(blockValueNode, ['type']);
if (typeNode && typeNode.type === 'string' && typeof typeNode.value === 'string') {
const blockType = typeNode.value;
if (blockType.startsWith('@')) {
return;
}

const link = createDocumentLinkForTypeNode(
typeNode,
parentNode,
textDocument,
root,
blockType,
);

if (link) {
links.push(link);
}
}

// Recursively process nested blocks
const nestedBlocksNode = findNodeAtLocation(blockValueNode, ['blocks']);
if (nestedBlocksNode) {
links.push(...processPresetBlocks(nestedBlocksNode, parentNode, textDocument, root));
}
});
} else if (blocksNode.type === 'array' && blocksNode.children) {
blocksNode.children.forEach((blockNode) => {
// Check if the block has a 'name' key
const nameNode = findNodeAtLocation(blockNode, ['name']);
if (nameNode) {
return; // Skip creating a link if 'name' key exists
}

const typeNode = findNodeAtLocation(blockNode, ['type']);
if (typeNode && typeNode.type === 'string' && typeof typeNode.value === 'string') {
const blockType = typeNode.value;
if (blockType.startsWith('@')) {
return;
}

const link = createDocumentLinkForTypeNode(
typeNode,
parentNode,
textDocument,
root,
blockType,
);

if (link) {
links.push(link);
}
}

// Recursively process nested blocks
const nestedBlocksNode = findNodeAtLocation(blockNode, ['blocks']);
if (nestedBlocksNode) {
links.push(...processPresetBlocks(nestedBlocksNode, parentNode, textDocument, root));
}
});
}

return links;
}

function createLinksFromBlocks(
blocksNode: JSONNode,
parentNode: LiquidRawTag,
textDocument: TextDocument,
root: URI,
): DocumentLink[] {
const links: DocumentLink[] = [];

if (blocksNode.children) {
blocksNode.children.forEach((blockNode: JSONNode) => {
// Check if the block has a 'name' key to avoid deeplinking inline block types
const nameNode = findNodeAtLocation(blockNode, ['name']);
if (nameNode) {
return;
}

const typeNode = findNodeAtLocation(blockNode, ['type']);
if (typeNode && typeNode.type === 'string' && typeof typeNode.value === 'string') {
const blockType = typeNode.value;
if (blockType.startsWith('@')) {
return;
}

const link = createDocumentLinkForTypeNode(
typeNode,
parentNode,
textDocument,
root,
blockType,
);

if (link) {
links.push(link);
}
}
});
}

return links;
}

0 comments on commit 6d5ccd2

Please sign in to comment.