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

Validate local section block references in presets and default configurations.
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,109 @@ describe('Module: ValidBlockTarget', () => {
const offenses = await check(theme, [ValidBlockTarget]);
expect(offenses).to.be.empty;
});

it('should report an unknown locally scoped block in a preset', async () => {
const theme: MockTheme = {
'sections/local-blocks.liquid': `
{% schema %}
{
"name": "Section name",
"blocks": [
{
"type": "local_block",
"name": "Local block"
}
],
"presets": [
{
"name": "Default preset",
"blocks": [
{
"type": "missing_block"
}
]
}
]
}
{% endschema %}
`,
};

const offenses = await check(theme, [ValidBlockTarget]);
expect(offenses).to.have.length(1);
expect(offenses).to.containOffense(
'Section block type "missing_block" must be defined in "blocks" at the root of this schema.',
);
});

it('should report an unknown locally scoped block in a default', async () => {
const theme: MockTheme = {
'sections/local-blocks.liquid': `
{% schema %}
{
"name": "Section name",
"blocks": [
{
"type": "local_block",
"name": "Local block"
}
],
"default": {
"blocks": [
{
"type": "missing_block"
}
]
}
}
{% endschema %}
`,
};

const offenses = await check(theme, [ValidBlockTarget]);
expect(offenses).to.have.length(1);
expect(offenses).to.containOffense(
'Section block type "missing_block" must be defined in "blocks" at the root of this schema.',
);
});

it('should allow locally scoped blocks declared for presets and defaults', async () => {
const theme: MockTheme = {
'sections/local-blocks.liquid': `
{% schema %}
{
"name": "Section name",
"blocks": [
{
"type": "local_block",
"name": "Local block"
}
],
"presets": [
{
"name": "Default preset",
"blocks": [
{
"type": "local_block"
}
]
}
],
"default": {
"blocks": [
{
"type": "local_block"
}
]
}
}
{% endschema %}
`,
};

const offenses = await check(theme, [ValidBlockTarget]);
expect(offenses).to.be.empty;
});
});

describe('Allowed Targeting Tests', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,23 @@ export const ValidBlockTarget: LiquidCheckDefinition = {
hasRootBlocksDeclaration,
} = getBlocks(validSchema);

if (rootLevelLocalBlocks.length > 0) return;
if (rootLevelLocalBlocks.length > 0) {
const localBlockTypes = new Set(rootLevelLocalBlocks.map(({ node }) => node.type));
const configuredBlocks = [...(presetLevelBlocks[0] ?? []), ...defaultLevelBlocks];

for (const { node, path } of configuredBlocks) {
if (!localBlockTypes.has(node.type)) {
const typeNode = nodeAtPath(ast, path)! as LiteralNode;
reportWarning(
`Section block type "${node.type}" must be defined in "blocks" at the root of this schema.`,
offset,
typeNode,
context,
);
}
}
return;
}

let errorsInRootLevelBlocks = false;
await Promise.all(
Expand Down
12 changes: 4 additions & 8 deletions packages/theme-check-common/src/utils/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,10 @@ export function getBlocks(validSchema: ThemeBlock.Schema | Section.Schema) {
}

function categorizeDefaultLevelBlocks(block: Preset.Block, index: number) {
const hasName = 'name' in block;

if (hasName) {
defaultLevelBlocks.push({
node: block,
path: ['default', 'blocks', String(index), 'type'],
});
}
defaultLevelBlocks.push({
node: block,
path: ['default', 'blocks', String(index), 'type'],
});
}

if (Array.isArray(rootLevelBlocks)) {
Expand Down
Loading