td-ai-tools 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/skills/README.md +3 -0
  3. package/skills/client-overview/SKILL.md +93 -0
  4. package/skills/client-overview/agents/openai.yaml +4 -0
  5. package/skills/client-overview/scripts/branch_client_overview_context.py +317 -0
  6. package/skills/debugging-ios-webkit/SKILL.md +60 -0
  7. package/skills/debugging-ios-webkit/references/device.md +62 -0
  8. package/skills/debugging-ios-webkit/references/playwright.md +60 -0
  9. package/skills/debugging-ios-webkit/references/simulator.md +76 -0
  10. package/skills/debugging-ios-webkit/scripts/device_eval.py +62 -0
  11. package/skills/debugging-ios-webkit/scripts/device_experiment.py +99 -0
  12. package/skills/debugging-ios-webkit/scripts/device_snapshot.py +62 -0
  13. package/skills/record-changes/SKILL.md +3 -3
  14. package/skills/shopify-lint/SKILL.md +50 -0
  15. package/skills/shopify-lint/agents/openai.yaml +4 -0
  16. package/skills/shopify-lint/scripts/setup.sh +60 -0
  17. package/skills/shopify-lint/scripts/shopify_lint.py +259 -0
  18. package/skills/shopify-lint/tests/test_shopify_lint.py +137 -0
  19. package/skills/shopify-lint/theme-check-theory/.theme-check.example.yml +14 -0
  20. package/skills/shopify-lint/theme-check-theory/README.md +123 -0
  21. package/skills/shopify-lint/theme-check-theory/configs/recommended.yml +8 -0
  22. package/skills/shopify-lint/theme-check-theory/package-lock.json +1947 -0
  23. package/skills/shopify-lint/theme-check-theory/package.json +44 -0
  24. package/skills/shopify-lint/theme-check-theory/src/checks/unguarded-text-setting.test.ts +198 -0
  25. package/skills/shopify-lint/theme-check-theory/src/checks/unguarded-text-setting.ts +143 -0
  26. package/skills/shopify-lint/theme-check-theory/src/checks/unused-section-settings.test.ts +137 -0
  27. package/skills/shopify-lint/theme-check-theory/src/checks/unused-section-settings.ts +64 -0
  28. package/skills/shopify-lint/theme-check-theory/src/index.test.ts +20 -0
  29. package/skills/shopify-lint/theme-check-theory/src/index.ts +11 -0
  30. package/skills/shopify-lint/theme-check-theory/src/test-utils.ts +31 -0
  31. package/skills/shopify-lint/theme-check-theory/src/utils/ast.ts +126 -0
  32. package/skills/shopify-lint/theme-check-theory/tsconfig.build.json +10 -0
  33. package/skills/shopify-lint/theme-check-theory/tsconfig.json +15 -0
  34. package/skills/shopify-lint/theme-check-theory/vitest.config.ts +11 -0
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "theme-check-theory",
3
+ "version": "0.1.0",
4
+ "description": "Theory Digital's custom Shopify Theme Check rules",
5
+ "type": "commonjs",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "files": [
10
+ "dist",
11
+ "configs",
12
+ ".theme-check.example.yml",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "prepack": "npm run build",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "keywords": [
22
+ "shopify",
23
+ "theme-check",
24
+ "liquid",
25
+ "lint"
26
+ ],
27
+ "license": "UNLICENSED",
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "peerDependencies": {
32
+ "@shopify/theme-check-common": ">=3.21.0 <4"
33
+ },
34
+ "dependencies": {
35
+ "@shopify/liquid-html-parser": "2.9.2",
36
+ "jsonc-parser": "3.3.1"
37
+ },
38
+ "devDependencies": {
39
+ "@shopify/theme-check-common": "3.27.0",
40
+ "@types/node": "24.0.15",
41
+ "typescript": "5.8.3",
42
+ "vitest": "3.2.7"
43
+ }
44
+ }
@@ -0,0 +1,198 @@
1
+ import {
2
+ check,
3
+ runLiquidCheck,
4
+ } from '@shopify/theme-check-common/dist/test/test-helper';
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import { liquidSection } from '../test-utils';
8
+ import {
9
+ DEFAULT_TEXTUAL_SETTING_TYPES,
10
+ UnguardedTextSetting,
11
+ } from './unguarded-text-setting';
12
+
13
+ describe('UnguardedTextSetting', () => {
14
+ it('reports raw outputs of all default text-like section and block setting types', async () => {
15
+ const settings = DEFAULT_TEXTUAL_SETTING_TYPES.map((type) => ({
16
+ type,
17
+ id: `section_${type}`,
18
+ }));
19
+ const blockSettings = DEFAULT_TEXTUAL_SETTING_TYPES.map((type) => ({
20
+ type,
21
+ id: `block_${type}`,
22
+ }));
23
+ const outputs = [
24
+ ...settings.map(({ id }) => `{{ section.settings.${id} }}`),
25
+ ...blockSettings.map(({ id }) => `{{ block.settings.${id} }}`),
26
+ ].join('\n');
27
+ const source = liquidSection(outputs, settings, [
28
+ { type: 'card', name: 'Card', settings: blockSettings },
29
+ ]);
30
+
31
+ const offenses = await runLiquidCheck(UnguardedTextSetting, source);
32
+
33
+ expect(offenses).toHaveLength(DEFAULT_TEXTUAL_SETTING_TYPES.length * 2);
34
+ expect(offenses[0].message).toBe(
35
+ "The text-like setting 'section_text' is output without a presence guard or default",
36
+ );
37
+ });
38
+
39
+ it('ignores non-textual settings by default', async () => {
40
+ const source = liquidSection(
41
+ `
42
+ {{ section.settings.enabled }}
43
+ {{ section.settings.spacing }}
44
+ {{ section.settings.image }}
45
+ `,
46
+ [
47
+ { type: 'checkbox', id: 'enabled' },
48
+ { type: 'range', id: 'spacing' },
49
+ { type: 'image_picker', id: 'image' },
50
+ ],
51
+ );
52
+
53
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
54
+ });
55
+
56
+ it.each([
57
+ [
58
+ 'if',
59
+ '{% if section.settings.heading %}{{ section.settings.heading }}{% endif %}',
60
+ ],
61
+ [
62
+ 'unless',
63
+ '{% unless section.settings.heading == blank %}{{ section.settings.heading }}{% endunless %}',
64
+ ],
65
+ [
66
+ 'elsif',
67
+ '{% if product %}x{% elsif section.settings.heading %}{{ section.settings.heading }}{% endif %}',
68
+ ],
69
+ [
70
+ 'case',
71
+ '{% case section.settings.heading %}{% when "Hello" %}{{ section.settings.heading }}{% endcase %}',
72
+ ],
73
+ ])('accepts a setting tested in an %s conditional', async (_, liquid) => {
74
+ const source = liquidSection(liquid, [{ type: 'text', id: 'heading' }]);
75
+
76
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
77
+ });
78
+
79
+ it('accepts logical and comparison presence tests', async () => {
80
+ const source = liquidSection(
81
+ `
82
+ {% if section.settings.heading != blank and section.settings.subtitle %}
83
+ {{ section.settings.heading }} {{ section.settings.subtitle }}
84
+ {% endif %}
85
+ `,
86
+ [
87
+ { type: 'text', id: 'heading' },
88
+ { type: 'textarea', id: 'subtitle' },
89
+ ],
90
+ );
91
+
92
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
93
+ });
94
+
95
+ it('does not mistake an unrelated enclosing condition for a setting presence guard', async () => {
96
+ const source = liquidSection(
97
+ '{% if product.available %}{{ section.settings.heading }}{% endif %}',
98
+ [{ type: 'text', id: 'heading' }],
99
+ );
100
+
101
+ const offenses = await runLiquidCheck(UnguardedTextSetting, source);
102
+
103
+ expect(offenses).toHaveLength(1);
104
+ });
105
+
106
+ it('accepts output with a default filter', async () => {
107
+ const source = liquidSection(
108
+ "{{ section.settings.heading | strip | default: 'Welcome' }}",
109
+ [{ type: 'text', id: 'heading' }],
110
+ );
111
+
112
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
113
+ });
114
+
115
+ it('does not treat use as another expression default as handling the setting', async () => {
116
+ const source = liquidSection(
117
+ "{{ product.title | default: section.settings.heading }} {{ section.settings.heading }}",
118
+ [{ type: 'text', id: 'heading' }],
119
+ );
120
+
121
+ const offenses = await runLiquidCheck(UnguardedTextSetting, source);
122
+
123
+ expect(offenses).toHaveLength(1);
124
+ });
125
+
126
+ it('applies handling coarsely to every output of that setting in the file', async () => {
127
+ const source = liquidSection(
128
+ `
129
+ {{ section.settings.heading }}
130
+ {% if section.settings.heading %}{{ section.settings.heading }}{% endif %}
131
+ {{ section.settings.heading }}
132
+ `,
133
+ [{ type: 'text', id: 'heading' }],
134
+ );
135
+
136
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
137
+ });
138
+
139
+ it('reports every raw output when the setting is never handled', async () => {
140
+ const source = liquidSection(
141
+ '{{ section.settings.heading }} / {{ section.settings.heading | escape }}',
142
+ [{ type: 'text', id: 'heading' }],
143
+ );
144
+
145
+ const offenses = await runLiquidCheck(UnguardedTextSetting, source);
146
+
147
+ expect(offenses).toHaveLength(2);
148
+ });
149
+
150
+ it('only considers variable output tags, not assignments or echo tags', async () => {
151
+ const source = liquidSection(
152
+ '{% assign heading = section.settings.heading %}{% echo section.settings.heading %}',
153
+ [{ type: 'text', id: 'heading' }],
154
+ );
155
+
156
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
157
+ });
158
+
159
+ it('supports textualSettingTypes configuration', async () => {
160
+ const source = liquidSection('{{ section.settings.image }}', [
161
+ { type: 'image_picker', id: 'image' },
162
+ ]);
163
+
164
+ const offenses = await check(
165
+ { 'sections/test.liquid': source },
166
+ [UnguardedTextSetting],
167
+ {},
168
+ {
169
+ UnguardedTextSetting: {
170
+ enabled: true,
171
+ textualSettingTypes: ['image_picker'],
172
+ },
173
+ },
174
+ );
175
+
176
+ expect(offenses).toHaveLength(1);
177
+ });
178
+
179
+ it('ignores dynamic output while still checking statically known outputs', async () => {
180
+ const source = liquidSection(
181
+ '{{ section.settings[key] }} {{ section.settings.heading }}',
182
+ [{ type: 'text', id: 'heading' }],
183
+ );
184
+
185
+ const offenses = await runLiquidCheck(UnguardedTextSetting, source);
186
+
187
+ expect(offenses).toHaveLength(1);
188
+ expect(offenses[0].message).toContain("'heading'");
189
+ });
190
+
191
+ it.each([
192
+ ['a file without schema', '{{ section.settings.heading }}'],
193
+ ['an invalid schema', '{{ section.settings.heading }}{% schema %}{ nope {% endschema %}'],
194
+ ])('stays silent for %s', async (_, source) => {
195
+ expect(await runLiquidCheck(UnguardedTextSetting, source)).toEqual([]);
196
+ });
197
+ });
198
+
@@ -0,0 +1,143 @@
1
+ import {
2
+ LiquidCheckDefinition,
3
+ SchemaProp,
4
+ Severity,
5
+ SourceCodeType,
6
+ } from '@shopify/theme-check-common';
7
+ import {
8
+ NodeTypes,
9
+ } from '@shopify/liquid-html-parser';
10
+ import type {
11
+ LiquidHtmlNode,
12
+ LiquidVariableOutput,
13
+ LiquidVariableLookup,
14
+ } from '@shopify/liquid-html-parser';
15
+
16
+ import {
17
+ getSettingDeclarations,
18
+ getSettingReference,
19
+ settingKey,
20
+ } from '../utils/ast';
21
+ import type { SettingDeclaration } from '../utils/ast';
22
+
23
+ export const DEFAULT_TEXTUAL_SETTING_TYPES = [
24
+ 'text',
25
+ 'textarea',
26
+ 'richtext',
27
+ 'inline_richtext',
28
+ 'html',
29
+ 'liquid',
30
+ 'url',
31
+ ] as const;
32
+
33
+ const schema = {
34
+ textualSettingTypes: SchemaProp.array(
35
+ SchemaProp.string(),
36
+ [...DEFAULT_TEXTUAL_SETTING_TYPES],
37
+ ),
38
+ };
39
+
40
+ export const UnguardedTextSetting: LiquidCheckDefinition<typeof schema> = {
41
+ meta: {
42
+ code: 'UnguardedTextSetting',
43
+ name: 'Require guards for text-like settings',
44
+ docs: {
45
+ description: 'Reports text-like settings output without a presence guard or default filter.',
46
+ recommended: true,
47
+ },
48
+ type: SourceCodeType.LiquidHtml,
49
+ severity: Severity.WARNING,
50
+ schema,
51
+ },
52
+ create(context) {
53
+ const declarations: SettingDeclaration[] = [];
54
+ const handledSettings = new Set<string>();
55
+ const outputs: Array<{
56
+ id: string;
57
+ key: string;
58
+ node: LiquidVariableOutput;
59
+ }> = [];
60
+
61
+ return {
62
+ async LiquidRawTag(node) {
63
+ const schemaDeclarations = getSettingDeclarations(node);
64
+ if (schemaDeclarations) declarations.push(...schemaDeclarations);
65
+ },
66
+
67
+ async LiquidVariableOutput(node) {
68
+ if (typeof node.markup === 'string') return;
69
+
70
+ const expression = node.markup.expression;
71
+ if (expression.type !== NodeTypes.VariableLookup) return;
72
+
73
+ const reference = getSettingReference(expression);
74
+ if (!reference || reference.dynamic || !reference.id) return;
75
+
76
+ const key = settingKey({ id: reference.id, scope: reference.scope });
77
+ outputs.push({ id: reference.id, key, node });
78
+
79
+ if (node.markup.filters.some(({ name }) => name === 'default')) {
80
+ handledSettings.add(key);
81
+ }
82
+ },
83
+
84
+ async VariableLookup(node, ancestors) {
85
+ const reference = getSettingReference(node);
86
+ if (!reference || reference.dynamic || !reference.id) return;
87
+ if (!isConditionalTest(node, ancestors)) return;
88
+
89
+ handledSettings.add(settingKey({ id: reference.id, scope: reference.scope }));
90
+ },
91
+
92
+ async onCodePathEnd() {
93
+ const textualTypes = new Set(context.settings.textualSettingTypes);
94
+ const textualSettings = new Set(
95
+ declarations
96
+ .filter(({ type }) => textualTypes.has(type))
97
+ .map(settingKey),
98
+ );
99
+
100
+ for (const output of outputs) {
101
+ if (!textualSettings.has(output.key) || handledSettings.has(output.key)) continue;
102
+
103
+ context.report({
104
+ message:
105
+ `The text-like setting '${output.id}' is output without a presence guard or default`,
106
+ startIndex: output.node.position.start,
107
+ endIndex: output.node.position.end,
108
+ });
109
+ }
110
+ },
111
+ };
112
+ },
113
+ };
114
+
115
+ function isConditionalTest(node: LiquidVariableLookup, ancestors: LiquidHtmlNode[]): boolean {
116
+ return ancestors.some((ancestor) => {
117
+ if (ancestor.type === NodeTypes.LiquidTag) {
118
+ if (!['if', 'unless', 'case'].includes(ancestor.name)) return false;
119
+ return containsNode(ancestor.markup, node);
120
+ }
121
+
122
+ if (ancestor.type === NodeTypes.LiquidBranch && ancestor.name === 'elsif') {
123
+ return containsNode(ancestor.markup, node);
124
+ }
125
+
126
+ return false;
127
+ });
128
+ }
129
+
130
+ function containsNode(markup: unknown, node: LiquidVariableLookup): boolean {
131
+ if (!markup || typeof markup !== 'object' || !('position' in markup)) return false;
132
+
133
+ const position = (markup as { position?: { start: number; end: number } }).position;
134
+ return Boolean(
135
+ position &&
136
+ position.start <= node.position.start &&
137
+ position.end >= node.position.end,
138
+ );
139
+ }
140
+
141
+ // v1 intentionally records a guard for the entire file. A branch-precise
142
+ // version can use the visitor `ancestors` argument to bind handling to the live
143
+ // LiquidBranch containing each output.
@@ -0,0 +1,137 @@
1
+ import { runLiquidCheck } from '@shopify/theme-check-common/dist/test/test-helper';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { liquidSection } from '../test-utils';
5
+ import { UnusedSectionSettings } from './unused-section-settings';
6
+
7
+ describe('UnusedSectionSettings', () => {
8
+ it('reports unused section and block settings', async () => {
9
+ const source = liquidSection(
10
+ `
11
+ <h2>{{ section.settings.heading }}</h2>
12
+ {% for block in section.blocks %}
13
+ <h3>{{ block.settings.block_heading }}</h3>
14
+ {% endfor %}
15
+ `,
16
+ [
17
+ { type: 'text', id: 'heading', label: 'Heading' },
18
+ { type: 'textarea', id: 'subtitle', label: 'Subtitle' },
19
+ ],
20
+ [
21
+ {
22
+ type: 'card',
23
+ name: 'Card',
24
+ settings: [
25
+ { type: 'text', id: 'block_heading', label: 'Heading' },
26
+ { type: 'url', id: 'block_link', label: 'Link' },
27
+ ],
28
+ },
29
+ ],
30
+ );
31
+
32
+ const offenses = await runLiquidCheck(
33
+ UnusedSectionSettings,
34
+ source,
35
+ 'sections/test.liquid',
36
+ );
37
+
38
+ expect(offenses.map(({ message }) => message)).toEqual([
39
+ "The setting 'subtitle' is declared but never referenced",
40
+ "The setting 'block_link' is declared but never referenced",
41
+ ]);
42
+ expect(offenses.every(({ check }) => check === 'UnusedSectionSettings')).toBe(true);
43
+ });
44
+
45
+ it('counts static references in outputs, tags, conditions, and bracket notation', async () => {
46
+ const source = liquidSection(
47
+ `
48
+ {% assign heading = section.settings.heading %}
49
+ {% if section.settings['subtitle'] %}
50
+ {{ heading }} — {{ section.settings["subtitle"] }}
51
+ {% endif %}
52
+ {% assign href = block.settings.link %}
53
+ `,
54
+ [
55
+ { type: 'text', id: 'heading' },
56
+ { type: 'textarea', id: 'subtitle' },
57
+ ],
58
+ [
59
+ {
60
+ type: 'card',
61
+ name: 'Card',
62
+ settings: [{ type: 'url', id: 'link' }],
63
+ },
64
+ ],
65
+ );
66
+
67
+ const offenses = await runLiquidCheck(UnusedSectionSettings, source);
68
+
69
+ expect(offenses).toEqual([]);
70
+ });
71
+
72
+ it.each([
73
+ ['section', '{{ section.settings[key] }}'],
74
+ ['block', '{{ block.settings[key] }}'],
75
+ ])('stays silent for the whole file when %s settings use dynamic access', async (_, liquid) => {
76
+ const source = liquidSection(
77
+ liquid,
78
+ [{ type: 'text', id: 'unused_section_setting' }],
79
+ [
80
+ {
81
+ type: 'card',
82
+ name: 'Card',
83
+ settings: [{ type: 'text', id: 'unused_block_setting' }],
84
+ },
85
+ ],
86
+ );
87
+
88
+ const offenses = await runLiquidCheck(UnusedSectionSettings, source);
89
+
90
+ expect(offenses).toEqual([]);
91
+ });
92
+
93
+ it('stays silent when the settings object itself escapes static analysis', async () => {
94
+ const source = liquidSection(
95
+ "{% render 'card', settings: section.settings %}",
96
+ [{ type: 'text', id: 'heading' }],
97
+ );
98
+
99
+ expect(await runLiquidCheck(UnusedSectionSettings, source)).toEqual([]);
100
+ });
101
+
102
+ it('reports each repeated declaration when a shared block setting id is unused', async () => {
103
+ const source = liquidSection('', [], [
104
+ {
105
+ type: 'card',
106
+ name: 'Card',
107
+ settings: [{ type: 'text', id: 'heading' }],
108
+ },
109
+ {
110
+ type: 'slide',
111
+ name: 'Slide',
112
+ settings: [{ type: 'text', id: 'heading' }],
113
+ },
114
+ ]);
115
+
116
+ const offenses = await runLiquidCheck(UnusedSectionSettings, source);
117
+
118
+ expect(offenses).toHaveLength(2);
119
+ });
120
+
121
+ it('ignores non-setting schema entries without ids', async () => {
122
+ const source = liquidSection('', [
123
+ { type: 'header', content: 'Content' } as never,
124
+ { type: 'paragraph', content: 'Help text' } as never,
125
+ ]);
126
+
127
+ expect(await runLiquidCheck(UnusedSectionSettings, source)).toEqual([]);
128
+ });
129
+
130
+ it.each([
131
+ ['a file without schema', '{{ section.settings.heading }}'],
132
+ ['an invalid schema', '{% schema %}{ nope {% endschema %}'],
133
+ ])('stays silent for %s', async (_, source) => {
134
+ expect(await runLiquidCheck(UnusedSectionSettings, source)).toEqual([]);
135
+ });
136
+ });
137
+
@@ -0,0 +1,64 @@
1
+ import {
2
+ LiquidCheckDefinition,
3
+ Severity,
4
+ SourceCodeType,
5
+ } from '@shopify/theme-check-common';
6
+
7
+ import {
8
+ getSettingDeclarations,
9
+ getSettingReference,
10
+ settingKey,
11
+ } from '../utils/ast';
12
+ import type { SettingDeclaration } from '../utils/ast';
13
+
14
+ export const UnusedSectionSettings: LiquidCheckDefinition = {
15
+ meta: {
16
+ code: 'UnusedSectionSettings',
17
+ name: 'Prevent unused section settings',
18
+ docs: {
19
+ description: 'Reports section and block settings that are never referenced in their file.',
20
+ recommended: true,
21
+ },
22
+ type: SourceCodeType.LiquidHtml,
23
+ severity: Severity.WARNING,
24
+ schema: {},
25
+ },
26
+ create(context) {
27
+ const declarations: SettingDeclaration[] = [];
28
+ const references = new Set<string>();
29
+ let hasDynamicAccess = false;
30
+
31
+ return {
32
+ async LiquidRawTag(node) {
33
+ const schemaDeclarations = getSettingDeclarations(node);
34
+ if (schemaDeclarations) declarations.push(...schemaDeclarations);
35
+ },
36
+
37
+ async VariableLookup(node) {
38
+ const reference = getSettingReference(node);
39
+ if (!reference) return;
40
+
41
+ if (reference.dynamic) {
42
+ hasDynamicAccess = true;
43
+ return;
44
+ }
45
+
46
+ references.add(settingKey(reference));
47
+ },
48
+
49
+ async onCodePathEnd() {
50
+ if (hasDynamicAccess) return;
51
+
52
+ for (const declaration of declarations) {
53
+ if (references.has(settingKey(declaration))) continue;
54
+
55
+ context.report({
56
+ message: `The setting '${declaration.id}' is declared but never referenced`,
57
+ startIndex: declaration.startIndex,
58
+ endIndex: declaration.endIndex,
59
+ });
60
+ }
61
+ },
62
+ };
63
+ },
64
+ };
@@ -0,0 +1,20 @@
1
+ import { Severity, SourceCodeType } from '@shopify/theme-check-common';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { checks } from './index';
5
+
6
+ describe('theme-check-theory module', () => {
7
+ it('exports both warning-level Liquid checks for Theme Check to load', () => {
8
+ expect(checks.map(({ meta }) => meta.code)).toEqual([
9
+ 'UnusedSectionSettings',
10
+ 'UnguardedTextSetting',
11
+ ]);
12
+
13
+ for (const check of checks) {
14
+ expect(check.meta.type).toBe(SourceCodeType.LiquidHtml);
15
+ expect(check.meta.severity).toBe(Severity.WARNING);
16
+ expect(check.meta.docs.recommended).toBe(true);
17
+ }
18
+ });
19
+ });
20
+
@@ -0,0 +1,11 @@
1
+ import { UnusedSectionSettings } from './checks/unused-section-settings';
2
+ import { UnguardedTextSetting } from './checks/unguarded-text-setting';
3
+
4
+ export { UnusedSectionSettings } from './checks/unused-section-settings';
5
+ export {
6
+ DEFAULT_TEXTUAL_SETTING_TYPES,
7
+ UnguardedTextSetting,
8
+ } from './checks/unguarded-text-setting';
9
+
10
+ export const checks = [UnusedSectionSettings, UnguardedTextSetting];
11
+
@@ -0,0 +1,31 @@
1
+ export type Setting = {
2
+ type: string;
3
+ id?: string;
4
+ label?: string;
5
+ };
6
+
7
+ export type Block = {
8
+ type: string;
9
+ name: string;
10
+ settings?: Setting[];
11
+ };
12
+
13
+ export function liquidSection(
14
+ liquid: string,
15
+ settings: Setting[] = [],
16
+ blocks: Block[] = [],
17
+ ): string {
18
+ const schema = JSON.stringify(
19
+ {
20
+ name: 'Test section',
21
+ settings,
22
+ blocks,
23
+ presets: [{ name: 'Test section' }],
24
+ },
25
+ null,
26
+ 2,
27
+ );
28
+
29
+ return `${liquid}\n{% schema %}\n${schema}\n{% endschema %}`;
30
+ }
31
+