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
8 changes: 8 additions & 0 deletions .changeset/schema-presets-and-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@shopify/theme-check-common': minor
'@shopify/theme-check-node': minor
---

Add `SchemaPresetsAndDefault` check

Reports section schemas that define both `presets` and `default`, matching the platform validation that rejects them with `Invalid schema: cannot define both 'default' and 'presets'`.
2 changes: 2 additions & 0 deletions packages/theme-check-common/src/checks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { MissingTemplate } from './missing-template';
import { OrphanedSnippet } from './orphaned-snippet';
import { PaginationSize } from './pagination-size';
import { ParserBlockingScript } from './parser-blocking-script';
import { SchemaPresetsAndDefault } from './schema-presets-and-default';
import { SchemaPresetsBlockOrder } from './schema-presets-block-order';
import { SchemaPresetsStaticBlocks } from './schema-presets-static-blocks';
import { RemoteAsset } from './remote-asset';
Expand Down Expand Up @@ -128,6 +129,7 @@ export const allChecks: (LiquidCheckDefinition | JSONCheckDefinition)[] = [
OrphanedSnippet,
PaginationSize,
ParserBlockingScript,
SchemaPresetsAndDefault,
SchemaPresetsBlockOrder,
SchemaPresetsStaticBlocks,
RemoteAsset,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect, describe, it } from 'vitest';
import { highlightedOffenses, runLiquidCheck } from '../../test';
import { SchemaPresetsAndDefault } from './index';

const DEFAULT_FILE_NAME = 'sections/file.liquid';

describe('Module: SchemaPresetsAndDefault', () => {
it('reports an error when a section defines both presets and default', async () => {
const sourceCode = `
{% schema %}
{
"name": "Test section",
"presets": [{ "name": "Preset 1" }],
"default": { "settings": {} }
}
{% endschema %}`;

const offenses = await runLiquidCheck(SchemaPresetsAndDefault, sourceCode, DEFAULT_FILE_NAME);
expect(offenses).toHaveLength(1);
expect(offenses[0].message).toEqual(
"Invalid schema: cannot define both 'default' and 'presets'",
);

const highlights = highlightedOffenses({ [DEFAULT_FILE_NAME]: sourceCode }, offenses);
expect(highlights).toEqual(['{ "settings": {} }']);
});

it('reports an error when presets is an empty array', async () => {
const sourceCode = `
{% schema %}
{
"name": "Test section",
"presets": [],
"default": {}
}
{% endschema %}`;

const offenses = await runLiquidCheck(SchemaPresetsAndDefault, sourceCode, DEFAULT_FILE_NAME);
expect(offenses).toHaveLength(1);
});

it('reports no error when a section only defines presets', async () => {
const sourceCode = `
{% schema %}
{
"name": "Test section",
"presets": [{ "name": "Preset 1" }]
}
{% endschema %}`;

const offenses = await runLiquidCheck(SchemaPresetsAndDefault, sourceCode, DEFAULT_FILE_NAME);
expect(offenses).toHaveLength(0);
});

it('reports no error when a section only defines default', async () => {
const sourceCode = `
{% schema %}
{
"name": "Test section",
"default": { "settings": {} }
}
{% endschema %}`;

const offenses = await runLiquidCheck(SchemaPresetsAndDefault, sourceCode, DEFAULT_FILE_NAME);
expect(offenses).toHaveLength(0);
});

it('does not run on theme blocks', async () => {
const sourceCode = `
{% schema %}
{
"name": "Test block",
"presets": [{ "name": "Preset 1" }]
}
{% endschema %}`;

const offenses = await runLiquidCheck(
SchemaPresetsAndDefault,
sourceCode,
'blocks/file.liquid',
);
expect(offenses).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getLocEnd, getLocStart, nodeAtPath } from '../../json';
import { getSchema, isSectionSchema } from '../../to-schema';
import { LiquidCheckDefinition, Severity, SourceCodeType } from '../../types';

export const SchemaPresetsAndDefault: LiquidCheckDefinition = {
meta: {
code: 'SchemaPresetsAndDefault',
name: 'Prevent sections from defining both presets and default',
docs: {
description:
'Reports section schemas that define both `presets` and `default`, which the platform rejects.',
recommended: true,
url: 'https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/schema-presets-and-default',
},
type: SourceCodeType.LiquidHtml,
severity: Severity.ERROR,
schema: {},
targets: [],
},

create(context) {
return {
async LiquidRawTag(node) {
if (node.name !== 'schema' || node.body.kind !== 'json') return;

const schema = await getSchema(context);
if (!isSectionSchema(schema)) return;

const { validSchema, ast } = schema;
if (!validSchema || validSchema instanceof Error) return;
if (!ast || ast instanceof Error) return;
if (validSchema.presets === undefined || validSchema.default === undefined) return;

const defaultNode = nodeAtPath(ast, ['default']);
if (!defaultNode) return;

const offset = node.blockStartPosition.end;
context.report({
message: "Invalid schema: cannot define both 'default' and 'presets'",
startIndex: offset + getLocStart(defaultNode),
endIndex: offset + getLocEnd(defaultNode),
});
},
};
},
};
3 changes: 3 additions & 0 deletions packages/theme-check-node/configs/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ ReservedDocParamNames:
SchemaOncePerFile:
enabled: true
severity: 0
SchemaPresetsAndDefault:
enabled: true
severity: 0
SchemaPresetsBlockOrder:
enabled: true
severity: 1
Expand Down
3 changes: 3 additions & 0 deletions packages/theme-check-node/configs/recommended.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ ReservedDocParamNames:
SchemaOncePerFile:
enabled: true
severity: 0
SchemaPresetsAndDefault:
enabled: true
severity: 0
SchemaPresetsBlockOrder:
enabled: true
severity: 1
Expand Down
Loading