Skip to content
Merged
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/block-settings-caller-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme-check-common': patch
---

Tell authors to replace `block.settings.<id>:` with `<id>:` when calling a `block`.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export function blockTagSyntaxError(
* Report the first one so body-form children stay outside the offense.
*/
const dottedArgument = markup.args.find(isDottedArgument);
if (dottedArgument) return syntaxProblem(dottedArgument.position, DOTTED_ARGUMENT);
if (dottedArgument) {
return syntaxProblem(dottedArgument.position, dottedArgumentMessage(dottedArgument.name));
}

if (markup.args.some(isInvalidBlockNameArgument)) {
return syntaxProblem(node.position, SYNTAX_ERROR);
Expand Down Expand Up @@ -109,3 +111,21 @@ function isInvalidBlockNameArgument(argument: BlockMarkup['args'][number]): bool
function isDottedArgument(argument: BlockMarkup['args'][number]): boolean {
return argument.name !== 'block.name' && argument.name.includes('.');
}

/*
* Temporary migration guidance for the experimental block.settings.<id>
* caller form. Remove it once authors have migrated to plain arguments.
*/
function dottedArgumentMessage(name: string): string {
const settingId = blockSettingId(name);
if (!settingId) return DOTTED_ARGUMENT;

return `Liquid syntax error: in 'block' - Use '${settingId}:' instead of '${name}:' when calling a block.`;
}

function blockSettingId(name: string): string | undefined {
const [object, property, settingId, ...rest] = name.split('.');
if (object !== 'block' || property !== 'settings' || rest.length > 0) return undefined;

return settingId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const BARE_BRACKET_ACCESS = 'Bare bracket access is not allowed in strict2 mode'
const DOTTED_ARGUMENT =
"Liquid syntax error: in 'block' - Use plain named arguments, for example: block 'name', heading: value";

function blockSettingArgumentMessage(settingId: string) {
return `Liquid syntax error: in 'block' - Use '${settingId}:' instead of 'block.settings.${settingId}:' when calling a block.`;
}

const UNCLOSED_DOC_PARSER_ERROR = "Attempting to end parsing before LiquidRawTag 'doc' was closed";
const UNOPENED_DOC_PARSER_ERROR =
"Attempting to close LiquidTag 'doc' before it was opened without a matching 'doc'";
Expand Down Expand Up @@ -811,12 +815,9 @@ describe('LiquidSyntaxError', () => {

describe('block caller arguments', () => {
it.each([
"block.settings.heading: 'Heading'",
'block.content: body',
"block.settings.heading.label: 'Heading'",
"block.unknown: 'value'",
"heading.label: 'Heading'",
])('rejects the unsupported dotted argument %s', async (argument) => {
['heading', "block.settings.heading: 'Heading'"],
['image_url', 'block.settings.image_url: product.featured_image'],
])('tells authors to pass %s as a plain argument', async (settingId, argument) => {
const template = `{% block 'card', ${argument} %}{% endblock %}`;
const offenses = await runLiquidCheck(
LiquidSyntaxError,
Expand All @@ -827,16 +828,20 @@ describe('LiquidSyntaxError', () => {

expect(offenses).toMatchObject([
{
message: DOTTED_ARGUMENT,
message: blockSettingArgumentMessage(settingId),
start: { index: template.indexOf(argument) },
end: { index: template.indexOf(argument) + argument.length },
},
]);
});

it('reports only the dotted argument in a body-form block', async () => {
const argument = "block.settings.heading: 'Heading'";
const template = `{% block 'card', ${argument} %}\n Child content\n{% endblock %}`;
it.each([
'block.content: body',
"block.settings.heading.label: 'Heading'",
"block.unknown: 'value'",
"heading.label: 'Heading'",
])('rejects the unsupported dotted argument %s', async (argument) => {
const template = `{% block 'card', ${argument} %}{% endblock %}`;
const offenses = await runLiquidCheck(
LiquidSyntaxError,
template,
Expand All @@ -853,9 +858,9 @@ describe('LiquidSyntaxError', () => {
]);
});

it('reports only the first of several dotted arguments', async () => {
const first = "block.settings.heading: 'Heading'";
const template = `{% block 'card', title: 'Title', ${first}, block.content: body, heading.label: 'Label' %}Child{% endblock %}`;
it('reports only the dotted argument in a body-form block', async () => {
const argument = "block.settings.heading: 'Heading'";
const template = `{% block 'card', ${argument} %}\n Child content\n{% endblock %}`;
const offenses = await runLiquidCheck(
LiquidSyntaxError,
template,
Expand All @@ -865,13 +870,50 @@ describe('LiquidSyntaxError', () => {

expect(offenses).toMatchObject([
{
message: DOTTED_ARGUMENT,
start: { index: template.indexOf(first) },
end: { index: template.indexOf(first) + first.length },
message: blockSettingArgumentMessage('heading'),
start: { index: template.indexOf(argument) },
end: { index: template.indexOf(argument) + argument.length },
},
]);
});

it.each([
[
"block.settings.heading: 'Heading'",
"block.content: body, heading.label: 'Label'",
blockSettingArgumentMessage('heading'),
],
[
'block.content: body',
"block.settings.heading: 'Heading', block.settings.count: 1",
DOTTED_ARGUMENT,
],
[
"block.settings.heading: 'Heading'",
'block.settings.count: 1',
blockSettingArgumentMessage('heading'),
],
])(
'reports only the first of several dotted arguments in %s, %s',
async (first, rest, message) => {
const template = `{% block 'card', title: 'Title', ${first}, ${rest} %}Child{% endblock %}`;
const offenses = await runLiquidCheck(
LiquidSyntaxError,
template,
'templates/test.liquid',
NO_DOCSET,
);

expect(offenses).toMatchObject([
{
message,
start: { index: template.indexOf(first) },
end: { index: template.indexOf(first) + first.length },
},
]);
},
);

it('accepts block.name with a string value alongside plain arguments', async () => {
const offenses = await runLiquidCheck(
LiquidSyntaxError,
Expand Down Expand Up @@ -941,12 +983,19 @@ describe('LiquidSyntaxError', () => {
});

it.each([
["block.settings.heading: 'Hello'", DOTTED_ARGUMENT],
["block.settings.heading: 'Hello'", blockSettingArgumentMessage('heading')],
['block.settings.count: 1', blockSettingArgumentMessage('count')],
['block.content: body', DOTTED_ARGUMENT],
["block.settings.heading.label: 'Hello'", DOTTED_ARGUMENT],
["heading.label: 'Hello'", DOTTED_ARGUMENT],
['block.name: value', "Syntax error in 'block' tag"],
["block.name: value, heading.label: 'Hello'", DOTTED_ARGUMENT],
["block.settings.heading: 'Hello', block.content: body", DOTTED_ARGUMENT],
['block.name: value, block.settings.count: 1', blockSettingArgumentMessage('count')],
[
"block.settings.heading: 'Hello', block.content: body",
blockSettingArgumentMessage('heading'),
],
["block.content: body, block.settings.heading: 'Hello'", DOTTED_ARGUMENT],
])('reports only one syntax error for %s', async (argument, message) => {
const offenses = await checkBlockCall(`${argument}, count: 'many', count: 'more'`);

Expand Down
Loading