diff --git a/.changeset/hungry-ravens-sneeze.md b/.changeset/hungry-ravens-sneeze.md new file mode 100644 index 000000000..c6185c760 --- /dev/null +++ b/.changeset/hungry-ravens-sneeze.md @@ -0,0 +1,7 @@ +--- +'@shopify/prettier-plugin-liquid': minor +'@shopify/liquid-html-parser': minor +'@shopify/theme-check-common': minor +--- + +Introducing `doc` tags inside the new `snippet` tag, as well as making the theme check more rigorous with where the `doc` tag is placed diff --git a/packages/theme-check-common/src/checks/unsupported-doc-tag/index.spec.ts b/packages/theme-check-common/src/checks/unsupported-doc-tag/index.spec.ts index 3a9f1b04c..5dca89a80 100644 --- a/packages/theme-check-common/src/checks/unsupported-doc-tag/index.spec.ts +++ b/packages/theme-check-common/src/checks/unsupported-doc-tag/index.spec.ts @@ -14,7 +14,7 @@ describe('Module: UnsupportedDocTag', () => { expect(offenses).to.have.length(1); expect(offenses[0].message).to.equal( - 'The `doc` tag can only be used within a snippet or block.', + 'The `doc` must be placed directly within an inline snippet tag, not nested inside other tags', ); }); @@ -42,4 +42,39 @@ describe('Module: UnsupportedDocTag', () => { expect(offenses).to.be.empty; }); + + it('should not report an error when `doc` tag is used inside inline snippet', async () => { + const layoutSourceCode = ` + {% snippet %} + ${sourceCode} + {% endsnippet %} + `; + + const offenses = await runLiquidCheck( + UnsupportedDocTag, + layoutSourceCode, + 'file://layout/theme.liquid', + ); + + expect(offenses).to.be.empty; + }); + + it('should report an error when `doc` tag is nested inside a block within a snippet file', async () => { + const nestedSourceCode = ` + {% if true %} + ${sourceCode} + {% endif %} + `; + + const offenses = await runLiquidCheck( + UnsupportedDocTag, + nestedSourceCode, + 'file://snippets/file.liquid', + ); + + expect(offenses).to.have.length(1); + expect(offenses[0].message).to.equal( + 'The `doc` tag cannot be nested inside any Liquid tags in a snippet or block file', + ); + }); }); diff --git a/packages/theme-check-common/src/checks/unsupported-doc-tag/index.ts b/packages/theme-check-common/src/checks/unsupported-doc-tag/index.ts index baaa5826b..b36b60afc 100644 --- a/packages/theme-check-common/src/checks/unsupported-doc-tag/index.ts +++ b/packages/theme-check-common/src/checks/unsupported-doc-tag/index.ts @@ -1,6 +1,7 @@ import { isSnippet, isBlock } from '../../to-schema'; import { LiquidCheckDefinition, Severity, SourceCodeType } from '../../types'; import { filePathSupportsLiquidDoc } from '../../liquid-doc/utils'; +import { LiquidRawTag, NodeTypes, LiquidHtmlNode } from '@shopify/liquid-html-parser'; export const UnsupportedDocTag: LiquidCheckDefinition = { meta: { @@ -19,18 +20,30 @@ export const UnsupportedDocTag: LiquidCheckDefinition = { create(context) { const docTagName = 'doc'; - - if (filePathSupportsLiquidDoc(context.file.uri)) { - return {}; - } + const snippetTagName = 'snippet'; return { - async LiquidRawTag(node) { + async LiquidRawTag(node: LiquidRawTag, ancestors: LiquidHtmlNode[]) { if (node.name !== docTagName) { return; } + + const isInSnippetOrBlockFile = filePathSupportsLiquidDoc(context.file.uri); + const immediateParent = ancestors.at(-1); + const isTopLevelInFile = immediateParent?.type === NodeTypes.Document; + const isDirectChildOfSnippetTag = + immediateParent?.type === NodeTypes.LiquidTag && immediateParent.name === snippetTagName; + + if ((isInSnippetOrBlockFile && isTopLevelInFile) || isDirectChildOfSnippetTag) { + return; + } + + const message = isInSnippetOrBlockFile + ? `The \`${docTagName}\` tag cannot be nested inside any Liquid tags in a snippet or block file` + : `The \`${docTagName}\` must be placed directly within an inline snippet tag, not nested inside other tags`; + context.report({ - message: `The \`${docTagName}\` tag can only be used within a snippet or block.`, + message, startIndex: node.position.start, endIndex: node.position.end, suggest: [