Skip to content

Commit

Permalink
Reworked
Browse files Browse the repository at this point in the history
  • Loading branch information
AribaRajput committed Dec 5, 2024
1 parent 0a7932b commit 07c3295
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ describe('ValidBlockPresetSettings', () => {
"id": "product",
"label": "t:settings.product"
},
{
"type": "collection",
"id": "collection",
"label": "t:settings.collection"
}
],
"presets": [
{
"name": "t:names.product_price",
"settings": {
"product": "{{ context.product }}",
"undefined_setting": "some value",
"undefined_setting": "some value"
}
}
]
Expand All @@ -32,24 +37,27 @@ describe('ValidBlockPresetSettings', () => {
};
const offenses = await check(theme, [ValidBlockPresetSettings]);
expect(offenses).to.have.length(1);
expect(offenses[0].message).to.include(
'Preset setting "undefined_setting" does not exist in settings',
);
});

it('should report invalid theme block preset settings', async () => {
it('should report invalid block preset settings', async () => {
const theme: MockTheme = {
'blocks/block_1.liquid': `
{% schema %}
{
"name": "t:names.block_1",
"settings": [
{
"type": "text",
"id": "block_1_setting_key",
"label": "t:settings.block_1"
},
]
}
{% endschema %}
`,
{% schema %}
{
"name": "t:names.block_1",
"settings": [
{
"type": "text",
"id": "block_1_setting_key",
"label": "t:settings.block_1"
}
]
}
{% endschema %}
`,
'blocks/price.liquid': `
{% schema %}
{
Expand All @@ -59,31 +67,44 @@ describe('ValidBlockPresetSettings', () => {
"type": "product",
"id": "product",
"label": "t:settings.product"
},
{
"type": "collection",
"id": "collection",
"label": "t:settings.collection"
}
],
"blocks": [
{
"type": "block_1",
"name": "t:names.block_1",
"name": "t:names.block_1"
}
],
"presets": [
{
"name": "t:names.product_price",
"settings": {
"settings": {
"product": "{{ context.product }}",
"collection": "{{ context.collection }}"
},
"blocks": [
{
"block_1": {
"type": "block_1",
"settings": {
"block_1_setting_key": "correct setting key",
"undefined_setting": "incorrect setting key"
"block_1_setting_key": "correct setting key",
"undefined_setting": "incorrect setting key"
}
}
}
],
]
},
{
"name": "t:names.product_price_2",
"settings": {
"product": "{{ context.product }}",
"collection": "{{ context.collection }}"
}
}
]
}
Expand All @@ -93,12 +114,13 @@ describe('ValidBlockPresetSettings', () => {

const offenses = await check(theme, [ValidBlockPresetSettings]);
expect(offenses).to.have.length(1);
console.log(offenses[0]);
expect(offenses[0].message).to.include(
'Preset setting "undefined_setting" does not exist in the block type "block_1"\'s settings',
`Preset block setting "undefined_setting" does not exist in settings`,
);
});

it('should not report when all section and block preset settings are valid', async () => {
it('should not report when all section and block preset settings in the block preset are valid', async () => {
const theme: MockTheme = {
'blocks/block_1.liquid': `
{% schema %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { basename } from '../../path';
import { LiquidCheckDefinition, Severity, SourceCodeType, ThemeBlock } from '../../types';
import { Preset } from '../../types/schemas/preset';
import { Setting } from '../../types/schemas/setting';
import { isError } from '../../utils/error';
import { nodeAtPath } from '../../json';
import { ObjectNode, PropertyNode } from 'json-to-ast';
import { BlockNodeWithPath, getBlocks } from '../valid-block-target/block-utils';

export const ValidBlockPresetSettings: LiquidCheckDefinition = {
meta: {
code: 'ValidBlockPresetSettings',
name: 'Reports invalid preset settings for a theme block',
name: 'Reports invalid preset settings for a block',
docs: {
description: 'Reports invalid preset settings for a theme block',
description: 'Reports invalid preset settings for a block',
recommended: true,
url: 'https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/valid-block-preset-settings',
},
Expand All @@ -32,82 +36,152 @@ export const ValidBlockPresetSettings: LiquidCheckDefinition = {
}
}

function getInlineSettingsTypesAndKeys(settings: Setting.Any[]) {
if (!settings) return [];
return settings.map((setting: { id: any; type: any }) => ({
id: setting.id,
type: setting.type,
}));
}
return {
async LiquidRawTag() {
const schema = await getSchema();
const { validSchema, ast } = schema ?? {};
if (!validSchema || validSchema instanceof Error) return;
if (!ast || ast instanceof Error) return;
if (!schema) return;

function getPresetSettingsKeys(presets: Preset.Preset[]) {
const allKeys: string[] = [];
for (const preset of presets) {
if (preset.settings) {
allKeys.push(...Object.keys(preset.settings));
}
}
return allKeys;
}
const presetNode = nodeAtPath(ast, ['presets']) as ObjectNode;
if (!presetNode) return;

function getPresetBlockSettingsKeys(blocks: Preset.PresetBlocks) {
const allKeys: string[] = [];
for (const block of Object.values(blocks)) {
for (const [_, blockData] of Object.entries(block)) {
if (blockData && typeof blockData === 'object' && 'settings' in blockData) {
const settings = blockData.settings;
if (settings && typeof settings === 'object') {
allKeys.push(...Object.keys(settings));
}
}
}
}
return allKeys;
}
const settingsNode = nodeAtPath(ast, ['settings']) as ObjectNode;
if (!settingsNode) return;

return {
async LiquidRawTag(node) {
if (node.name !== 'schema' || node.body.kind !== 'json') {
return;
}
const presetSettingsIds = presetNode.children
.map((preset: PropertyNode) => {
const settingsNode = preset.children.find(
(prop: PropertyNode) => prop.key.value === 'settings',
);
if (settingsNode?.value?.children) {
return settingsNode.value.children.map((setting: PropertyNode) => {
const key = setting.key.value;
const start = setting.key.loc?.start;
const end = setting.key.loc?.end;
return { key, start, end };
});
}
return [];
})
.flat()
.filter(Boolean);

const schema = await getSchema();
if (!schema) return;
if (schema.validSchema instanceof Error) return;
const settingIds = settingsNode.children.map((child: PropertyNode) => {
const idNode = child.children.find((prop: PropertyNode) => prop.key.value === 'id');
return idNode?.value?.value; // Access the actual ID string
});

const validSchema = schema.validSchema;
const settingsKeys = getInlineSettingsTypesAndKeys(validSchema.settings);
const presetSettingsKeys = getPresetSettingsKeys(validSchema.presets ?? []);

for (const key of presetSettingsKeys) {
if (!settingsKeys.some((setting) => setting.id === key)) {
for (const presetSettingId of presetSettingsIds) {
if (!settingIds.includes(presetSettingId.key)) {
context.report({
message: `Preset setting "${key}" does not exist in the block's settings`,
startIndex: 0,
endIndex: 0,
startIndex: presetSettingId.start.line,
endIndex: presetSettingId.end.line,
message: `Preset setting "${presetSettingId.key}" does not exist in settings`,
});
}
}

if (validSchema.blocks) {
for (const block of validSchema.blocks) {
const blockSchema = await context.getBlockSchema?.(block.type);
if (!blockSchema || blockSchema.validSchema instanceof Error) continue;

for (const preset of validSchema.presets ?? []) {
if (!preset.blocks) continue;
const presetBlockSettingKeys = getPresetBlockSettingsKeys(preset.blocks) ?? [];

for (const key of presetBlockSettingKeys) {
if (!blockSchema.validSchema.settings?.some((setting) => setting.id === key)) {
context.report({
message: `Preset setting "${key}" does not exist in the block type "${block.type}"'s settings`,
startIndex: 0,
endIndex: 0,
});
}
const { rootLevelThemeBlocks, rootLevelLocalBlocks, presetLevelBlocks } =
getBlocks(validSchema);


const rootLevelThemeBlockSettingIds = await Promise.all(
rootLevelThemeBlocks.map(async ({ node }) => {
const blockSchema = await context.getBlockSchema?.(node.type);
const { validSchema, ast } = blockSchema ?? {};

if (!validSchema || validSchema instanceof Error) return [];
if (!ast || ast instanceof Error) return [];

const settingsNode = nodeAtPath(ast, ['settings']) as ObjectNode;
if (!settingsNode?.children) return [];

return settingsNode.children
.map((child: PropertyNode) => {
const idNode = child.children?.find(
(prop: PropertyNode) => prop.key.value === 'id'
);
return idNode?.value?.value;
})
.filter(Boolean);
})
);

const rootLevelLocalBlockSettingIds = await Promise.all(rootLevelLocalBlocks
.map(async ({ node }) => {
if (!node.settings) {
const blockType = node.type;
const blockSchema = await context.getBlockSchema?.(blockType);
const { validSchema, ast } = blockSchema ?? {};

if (!validSchema || validSchema instanceof Error) return [];
if (!ast || ast instanceof Error) return [];

const settingsNode = nodeAtPath(ast, ['settings']) as ObjectNode;
if (!settingsNode?.children) return [];

return settingsNode.children
.map((child: PropertyNode) => {
const idNode = child.children?.find(
(prop: PropertyNode) => prop.key.value === 'id'
);
return idNode?.value?.value;
})
.filter(Boolean);
}
}
return Object.keys(node.settings);
}))
.then(ids => ids.flat());
console.log(rootLevelLocalBlockSettingIds);

//one array to compare against
const allBlockSettingIds = [
...rootLevelThemeBlockSettingIds,
...rootLevelLocalBlockSettingIds
].flat();

console.log(allBlockSettingIds);
//we check this against the other two arrays
const presetBlockSettingIds = Object.values(presetLevelBlocks)
.flat()
.map(({ node }) => {
const blockKey = Object.keys(node).find(
(key) => node[key as keyof typeof node]?.settings,
);
if (!blockKey) return [];

const blockSettingsNode = nodeAtPath(ast, [
'presets',
'0',
'blocks',
'0',
blockKey,
'settings',
]) as ObjectNode;
if (!blockSettingsNode?.children) return [];

return blockSettingsNode.children
.filter((node): node is PropertyNode => 'key' in node)
.map((setting) => ({
key: setting.key.value,
start: setting.key.loc?.start,
end: setting.key.loc?.end,
}));
})
.flat()
.filter(Boolean);


for (const presetBlockSettingId of presetBlockSettingIds) {
if (!allBlockSettingIds.includes(presetBlockSettingId.key)) {
context.report({
startIndex: presetBlockSettingId?.start?.line ?? 0,
endIndex: presetBlockSettingId?.end?.line ?? 0,
message: `Preset block setting "${presetBlockSettingId.key}" does not exist in settings.`,
});
}
}
},
Expand Down

0 comments on commit 07c3295

Please sign in to comment.