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/contextual-objects-in-snippets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme-check-common': patch
---

Recognise contextual Liquid objects in snippets based on the files that render them.
30 changes: 20 additions & 10 deletions packages/theme-check-common/src/checks/undefined-object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '../../types';
import { isError, last } from '../../utils';
import { hasLiquidDoc } from '../../liquid-doc/liquidDoc';
import { getAncestorUris } from '../../utils/traversal';
import { isWithinRawTagThatDoesNotParseItsContents } from '../utils';

type Scope = { start?: number; end?: number };
Expand Down Expand Up @@ -156,7 +157,7 @@ export const UndefinedObject: LiquidCheckDefinition = {
},

async onCodePathEnd() {
const objects = await globalObjects(themeDocset, relativePath, context.mode);
const objects = await globalObjects(themeDocset, context);

objects.forEach((obj) => fileScopedVariables.add(obj.name));
(await themeBlockParameterNames(context)).forEach((name) => fileScopedVariables.add(name));
Expand All @@ -183,20 +184,29 @@ export const UndefinedObject: LiquidCheckDefinition = {
},
};

async function globalObjects(themeDocset: ThemeDocset, relativePath: string, mode: Mode = 'theme') {
async function globalObjects(
themeDocset: ThemeDocset,
context: Context<SourceCodeType.LiquidHtml>,
) {
const objects = await themeDocset.objects();
const contextualObjects = getContextualObjects(relativePath, mode);
const relativePath = context.toRelativePath(context.file.uri);
const contextualObjects = new Set(getContextualObjects(relativePath, context.mode));

if (relativePath.startsWith('snippets/') && context.getReferences) {
const ancestors = await getAncestorUris(context.file.uri, context.getReferences);
ancestors.forEach((uri) => {
const ancestorPath = context.toRelativePath(uri);
getContextualObjects(ancestorPath, context.mode).forEach((name) =>
contextualObjects.add(name),
);
});
}

const globalObjects = objects.filter(({ access, name }) => {
return objects.filter(({ access, name }) => {
return (
contextualObjects.includes(name) ||
!access ||
access.global === true ||
access.template.length > 0
contextualObjects.has(name) || !access || access.global === true || access.template.length > 0
);
});

return globalObjects;
}

/** Theme blocks always receive the built-in `content` parameter as a variable. */
Expand Down
18 changes: 18 additions & 0 deletions specs/checks/undefined-object.yml
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,24 @@ cases:
message: "Unknown object 'recommendations' used."
start: [0, 47]
highlight: recommendations
- name: should allow section contextual objects in a snippet referenced by that section
mode: theme
files:
sections/predictive-search.liquid: "{% render 'predictive-search-results' %}"
snippets/predictive-search-results.liquid: "{% doc %} @param {string} x - x {% enddoc %}{{ predictive_search }}"
docset: default
references:
- source: sections/predictive-search.liquid
target: snippets/predictive-search-results.liquid
- name: should report section contextual objects in an unreferenced snippet
files:
snippets/my-snippet.liquid: "{% doc %} @param {string} x - x {% enddoc %}{{ predictive_search }}"
docset: default
offenses:
- file: snippets/my-snippet.liquid
message: "Unknown object 'predictive_search' used."
start: [0, 47]
highlight: predictive_search
- name: should support contextual exceptions for checkout.liquid (1)
files:
layout/checkout.liquid: "{{ locale }}"
Expand Down
Loading