@shopify/theme-language-server-common
Version:
<h1 align="center" style="position: relative;" > <br> <img src="https://github.com/Shopify/theme-check-vscode/blob/main/images/shopify_glyph.png?raw=true" alt="logo" width="141" height="160"> <br> Theme Language Server </h1>
153 lines (129 loc) • 4.4 kB
text/typescript
import { DocsetEntry, FilterEntry, ObjectEntry, TagEntry } from '@shopify/theme-check-common';
import { ArrayType, PseudoType, docsetEntryReturnType, isArrayType } from '../TypeSystem';
import { Attribute, Tag, Value } from './HtmlDocset';
const HORIZONTAL_SEPARATOR = '\n\n---\n\n';
export type HtmlEntry = Tag | Attribute | Value;
export type DocsetEntryType = 'filter' | 'tag' | 'object';
export function render(
entry: DocsetEntry | FilterEntry | TagEntry,
returnType?: PseudoType | ArrayType,
docsetEntryType?: DocsetEntryType,
) {
return [title(entry, returnType), docsetEntryBody(entry, returnType, docsetEntryType)]
.filter(Boolean)
.join('\n');
}
export function renderHtmlEntry(entry: HtmlEntry, parentEntry?: HtmlEntry) {
return [title(entry, 'untyped'), htmlEntryBody(entry, parentEntry)].join('\n');
}
function title(
entry: DocsetEntry | ObjectEntry | FilterEntry | HtmlEntry,
returnType?: PseudoType | ArrayType,
) {
returnType = returnType ?? docsetEntryReturnType(entry as ObjectEntry, 'untyped');
if (isArrayType(returnType)) {
return `### ${entry.name}: \`${returnType.valueType}[]\``;
} else if (returnType !== 'untyped') {
return `### ${entry.name}: \`${returnType}\``;
}
return `### ${entry.name}`;
}
function sanitize(s: string | undefined) {
return s
?.replace(/(^|\n+)>/g, ' ')
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/\]\(\//g, '](https://shopify.dev/')
.trim();
}
function docsetEntryBody(
entry: DocsetEntry,
returnType?: PseudoType | ArrayType,
docsetEntryType?: DocsetEntryType,
) {
return [
syntax(entry),
entry.deprecation_reason,
entry.summary,
entry.description,
shopifyDevReference(entry, returnType, docsetEntryType),
]
.map(sanitize)
.filter(Boolean)
.join(HORIZONTAL_SEPARATOR);
}
function htmlEntryBody(entry: HtmlEntry, parentEntry?: HtmlEntry) {
return [description(entry), references(entry), references(parentEntry)]
.filter(Boolean)
.join(HORIZONTAL_SEPARATOR);
}
function syntax(entry: DocsetEntry | FilterEntry | TagEntry) {
if (!('syntax' in entry) || !entry.syntax) {
return undefined;
}
// TagEntry entries already have liquid tags as a part of the syntax
// explanation so we can return them directly.
if (entry.syntax.startsWith('{%')) {
return `\`\`\`liquid\n${entry.syntax}\n\`\`\``;
}
// Wrap the syntax in liquid tags to ensure we get proper syntax highlighting
// if it's available.
return `\`\`\`liquid\n{{ ${entry.syntax} }}\n\`\`\``;
}
function description(entry: HtmlEntry) {
if (!entry.description || typeof entry.description === 'string') {
return entry.description;
}
return entry.description.value;
}
const shopifyDevRoot = `https://shopify.dev/docs/api/liquid`;
function shopifyDevReference(
entry: DocsetEntry,
returnType?: PseudoType | ArrayType,
docsetEntryType?: DocsetEntryType,
) {
switch (docsetEntryType) {
case 'tag': {
if (entry.name === 'else' && 'category' in entry) {
return `[Shopify Reference](${shopifyDevRoot}/tags/${entry.category}-${entry.name})`;
} else if ('category' in entry) {
return `[Shopify Reference](${shopifyDevRoot}/tags/${entry.name})`;
} else {
return undefined;
}
}
case 'object': {
if (!returnType) {
return `[Shopify Reference](${shopifyDevRoot}/objects/${entry.name})`;
} else if (isArrayType(returnType)) {
return `[Shopify Reference](${shopifyDevRoot}/objects/${returnType.valueType})`;
} else if ('access' in entry) {
return `[Shopify Reference](${shopifyDevRoot}/objects/${returnType})`;
} else {
return undefined;
}
}
case 'filter': {
if ('category' in entry) {
return `[Shopify Reference](${shopifyDevRoot}/filters/${entry.name})`;
} else {
return undefined;
}
}
default: {
return undefined;
}
}
}
function references(entry: HtmlEntry | undefined) {
if (!entry || !('references' in entry) || !entry.references || entry.references.length === 0) {
return undefined;
}
if (entry.references.length === 1) {
const [ref] = entry.references;
return `[${ref.name}](${ref.url})`;
}
return [`#### Learn more`, entry.references.map((ref) => `- [${ref.name}](${ref.url})`)].join(
'\n\n',
);
}