-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
8 changed files
with
296 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@shopify/theme-check-common': minor | ||
'theme-check-vscode': minor | ||
--- | ||
|
||
Add the `ValidBlockPresetSettings` check. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
25 changes: 0 additions & 25 deletions
25
packages/theme-check-common/src/checks/presets-key-matches/index.ts
This file was deleted.
Oops, something went wrong.
166 changes: 166 additions & 0 deletions
166
packages/theme-check-common/src/checks/valid-block-preset-settings/index.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { ValidBlockPresetSettings } from '.'; | ||
import { check } from '../../test/test-helper'; | ||
import { MockTheme } from '../../test/MockTheme'; | ||
|
||
describe('ValidBlockPresetSettings', () => { | ||
it('should report invalid preset settings', async () => { | ||
const theme: MockTheme = { | ||
'blocks/price.liquid': ` | ||
{% schema %} | ||
{ | ||
"name": "t:names.product_price", | ||
"settings": [ | ||
{ | ||
"type": "product", | ||
"id": "product", | ||
"label": "t:settings.product" | ||
}, | ||
], | ||
"presets": [ | ||
{ | ||
"name": "t:names.product_price", | ||
"settings": { | ||
"product": "{{ context.product }}", | ||
"undefined_setting": "some value", | ||
} | ||
} | ||
] | ||
} | ||
{% endschema %} | ||
`, | ||
}; | ||
const offenses = await check(theme, [ValidBlockPresetSettings]); | ||
expect(offenses).to.have.length(1); | ||
}); | ||
|
||
it('should report invalid theme 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 %} | ||
`, | ||
'blocks/price.liquid': ` | ||
{% schema %} | ||
{ | ||
"name": "t:names.product_price", | ||
"settings": [ | ||
{ | ||
"type": "product", | ||
"id": "product", | ||
"label": "t:settings.product" | ||
} | ||
], | ||
"blocks": [ | ||
{ | ||
"type": "block_1", | ||
"name": "t:names.block_1", | ||
} | ||
], | ||
"presets": [ | ||
{ | ||
"name": "t:names.product_price", | ||
"settings": { | ||
"product": "{{ context.product }}", | ||
}, | ||
"blocks": [ | ||
{ | ||
"block_1": { | ||
"type": "block_1", | ||
"settings": { | ||
"block_1_setting_key": "correct setting key", | ||
"undefined_setting": "incorrect setting key" | ||
} | ||
} | ||
} | ||
], | ||
} | ||
] | ||
} | ||
{% endschema %} | ||
`, | ||
}; | ||
|
||
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 the block type "block_1"\'s settings', | ||
); | ||
}); | ||
|
||
it('should not report when all section and block preset settings are valid', 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 %} | ||
`, | ||
'blocks/price.liquid': ` | ||
{% schema %} | ||
{ | ||
"name": "t:names.product_price", | ||
"settings": [ | ||
{ | ||
"type": "product", | ||
"id": "product", | ||
"label": "t:settings.product" | ||
}, | ||
{ | ||
"type": "text", | ||
"id": "section_setting", | ||
"label": "t:settings.section" | ||
} | ||
], | ||
"blocks": [ | ||
{ | ||
"type": "block_1", | ||
"name": "t:names.block_1" | ||
} | ||
], | ||
"presets": [ | ||
{ | ||
"name": "t:names.product_price", | ||
"settings": { | ||
"product": "{{ context.product }}", | ||
"section_setting": "some value" | ||
}, | ||
"blocks": [ | ||
{ | ||
"block_1": { | ||
"type": "block_1", | ||
"settings": { | ||
"block_1_setting_key": "correct setting key" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
{% endschema %} | ||
`, | ||
}; | ||
|
||
const offenses = await check(theme, [ValidBlockPresetSettings]); | ||
expect(offenses).to.have.length(0); | ||
}); | ||
}); |
116 changes: 116 additions & 0 deletions
116
packages/theme-check-common/src/checks/valid-block-preset-settings/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { isSection, isBlock } from '../../to-schema'; | ||
import { basename } from '../../path'; | ||
import { LiquidCheckDefinition, Severity, SourceCodeType, ThemeBlock } from '../../types'; | ||
import { Preset } from '../../types/schemas/preset'; | ||
import { Setting } from '../../types/schemas/setting'; | ||
|
||
export const ValidBlockPresetSettings: LiquidCheckDefinition = { | ||
meta: { | ||
code: 'ValidBlockPresetSettings', | ||
name: 'Reports invalid preset settings for a theme block', | ||
docs: { | ||
description: 'Reports invalid preset settings for a theme block', | ||
recommended: true, | ||
url: 'https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/valid-block-preset-settings', | ||
}, | ||
severity: Severity.ERROR, | ||
type: SourceCodeType.LiquidHtml, | ||
schema: {}, | ||
targets: [], | ||
}, | ||
|
||
create(context) { | ||
function getSchema() { | ||
const name = basename(context.file.uri, '.liquid'); | ||
switch (true) { | ||
case isBlock(context.file.uri): | ||
return context.getBlockSchema?.(name); | ||
case isSection(context.file.uri): | ||
return context.getSectionSchema?.(name); | ||
default: | ||
return undefined; | ||
} | ||
} | ||
|
||
function getInlineSettingsTypesAndKeys(settings: Setting.Any[]) { | ||
if (!settings) return []; | ||
return settings.map((setting: { id: any; type: any }) => ({ | ||
id: setting.id, | ||
type: setting.type, | ||
})); | ||
} | ||
|
||
function getPresetSettingsKeys(presets: Preset.Preset[]) { | ||
const allKeys: string[] = []; | ||
for (const preset of presets) { | ||
if (preset.settings) { | ||
allKeys.push(...Object.keys(preset.settings)); | ||
} | ||
} | ||
return allKeys; | ||
} | ||
|
||
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; | ||
} | ||
|
||
return { | ||
async LiquidRawTag(node) { | ||
if (node.name !== 'schema' || node.body.kind !== 'json') { | ||
return; | ||
} | ||
|
||
const schema = await getSchema(); | ||
if (!schema) return; | ||
if (schema.validSchema instanceof Error) return; | ||
|
||
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)) { | ||
context.report({ | ||
message: `Preset setting "${key}" does not exist in the block's settings`, | ||
startIndex: 0, | ||
endIndex: 0, | ||
}); | ||
} | ||
} | ||
|
||
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, | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters